Subagents receive a CLAUDE.md/memory snapshot from parent session start, not spawn — undocumented, no way to refresh

Status Open
Reported on v2.1.237
Maintainer reply None cached
Activity 5 comments · opened Aug 22, 2026

Version: 2.1.237 (Linux)

What happens

When a subagent is spawned via the Agent tool, the CLAUDE.md and memory content in its context is not read from disk at spawn time. It is the copy the parent session captured when the parent started.

In a long-running session this drifts arbitrarily far. Measured in one session:

  • parent session started 12:31, subagent dispatched 21:10 — the subagent's copy was 8h39m old
  • a memory file DELETED from disk at 12:50 was still present in the subagent's context, and its index still pointed at the (now nonexistent) path
  • three memory files WRITTEN after 12:31 were absent entirely
  • the deleted file contained a rule that had been reversed; the subagent would have acted on the reversed version

Nothing in the subagent's context marks any of it as stale. A superseded instruction reads exactly like a current one, so the agent proceeds confidently and produces a correct-looking result from an out-of-date rule. A missing file announces itself when you reach for it; a stale one never does.

Repro

  1. Start a session in a project with CLAUDE.md and memory files.
  2. Edit CLAUDE.md and add/delete a memory file.
  3. Spawn a subagent and ask it to print what it received.
  4. It reports the pre-edit content.

What doesn't help

  • SessionStart and UserPromptSubmit hooks do not fire on subagent dispatch (verified). There is no SubagentStart event.
  • No agent-definition frontmatter field controls context injection or refresh.
  • No setting or env var appears to affect it.
  • Reading or editing memory in the parent mid-session does not refresh the parent's cached copy, so children keep inheriting the old one.
  • Forks inherit the parent conversation, which carries the corrections, but not a fresh snapshot — and at full context cost.

Asks

  1. Document the actual behaviour — whether it's a snapshot, when it's captured. Right now the docs say subagents load CLAUDE.md and memory, which reads as a live read.
  2. Some way to opt into a fresh read at spawn, or a SubagentStart hook so a project can inject current state itself.

Workaround in use: a script the subagent runs as step one, which diffs its context files against git and prints what moved since the session directory's birth time. It works, but every project has to invent it, and it only works for memory that lives in git.

View original on GitHub ↗

5 Comments

secondbrainstarter · 6 days ago

The sharpest detail in this report is the asymmetry you named at the end: a missing file announces itself when reached for, a stale one never does. That maps onto a distinction we ended up building after hitting the same class of failure in our own vault-based memory setup — between freshness defects and provenance defects:

  1. Freshness (what this issue describes): the snapshot is old but internally consistent. Detection needs an age marker at load time — even just a "memory loaded N minutes ago from disk" line in the subagent's context would make the drift visible without changing behavior.
  1. Provenance (the second-order damage): a file deleted on disk still referenced by an index is a dangling pointer, and a reversed rule that still carries its original authority is worse than a missing one because it gets acted on confidently. We classify those as breaches rather than hygiene issues — exit code 2 vs 1 — because they change what the agent does, not just how tidy the store is.

One cheap mitigation for anyone hitting this today: date-stamp every memory entry at write time and have the subagent re-state entry dates when citing a rule from memory. It doesn't fix the snapshot semantics, but it converts silent staleness into something the model can notice and flag mid-task.

The write-time vs read-time framing from #79217 applies here too: refreshing at spawn fixes read time, but the deeper question is whether subagents should inherit memory by copy at all versus by reference with a generation counter.

shafee1913 · 5 days ago

Can confirm the freshness/provenance split matches what we see in practice — we run subagents constantly against a git-versioned memory directory, and the provenance case is the one that actually bites: a rule that was amended after the parent session booted arrives in the subagent looking exactly as authoritative as everything else. Nothing in the context marks the gap.

Our working mitigation, for anyone else here: memory lives in a git repo, and the memory files themselves instruct any subagent to run a check-on-arrival step — git log on the memory repo, diff the loaded copy against disk, disk wins on disagreement. It works precisely because the instruction rides inside the snapshot: the copy teaches distrust of itself. But it's convention, not wiring — an agent that skips the step inherits stale rules with full confidence, which is your exit-code-2 case.

On copy vs reference: after a few weeks of this, my vote is that copy-inheritance is the bug, not the missing refresh. A refresh-at-spawn fixes the timestamp but keeps the semantics implicit; a reference with a generation counter (or even just a memory_loaded_at line injected into the subagent's context, as you suggest) would let the model know what it's holding. Right now the only way an agent learns its memory is a snapshot is us telling it — and that's the part that should be documentation at minimum.

shafee1913 · 5 days ago

Some prior art, from a proper search I should have run before filing. None of it makes this a duplicate, but four issues are close kin:

  • #87613 — proxy-captured request bodies show the parent's auto-memory is delivered to subagents inside the # claudeMd block (~34% of a small subagent's payload), while the docs' "never reaches the subagent" list says it isn't loaded. Same payload block as this issue, complementary claim: that one is about the content being there at all (and its cost); this one is about the copy being stale and the capture point undocumented.
  • #81361isolation: "worktree" builds the worktree from the session-start HEAD and session-start dirty files, not current state; a subagent re-implemented 15 commits of finished work. Same snapshot-at-parent-boot mechanism on a different surface — evidence the behavior is structural, not memory-specific.
  • #85075 — a months-old MEMORY.md loads silently with no freshness indication. The surface it asks for (modified-N-days-ago at load) is the same one that would make the staleness described here visible.
  • #69571 — CLAUDE.md is not hot-reloaded mid-session (closed by stale-bot, never answered by a human). Its reporter measured that /compact does trigger a CLAUDE.md re-read in the parent — a partial correction to the "what doesn't help" list above; though per #81361 the worktree snapshot survives compaction, so the child-side behavior appears unchanged.

On the documentation point specifically: the sub-agents docs page states capture time explicitly for two items in its startup list — git status is "a snapshot taken at the start of the parent session," the sibling agent roster is "a snapshot taken when the subagent starts" — and gives no timing qualifier at all for CLAUDE.md/memory. The capture point isn't just undocumented; the page demonstrates it knows how to state capture-time and is silent on exactly the item in question.

(@secondbrainstarter — one small correction: #79217 is the MEMORY.md index-size-limit request; the write-time/read-time asymmetry lives in one of its buried comments, not its subject. #82056 states the load-observability problem as its own thesis and is probably the cite you want.)

secondbrainstarter · 5 days ago

The generation-counter vote matches what we landed on from the other side: our vault checks re-read from disk every run and treat the loaded copy as advisory, so nothing downstream ever trusts a snapshot it can't see. But your check-on-arrival step has the sharper lesson: it works because the copy teaches distrust of itself, and it fails exactly when convention replaces wiring — which is why we made ours a script with exit codes instead of prose instructions. An instruction can be skipped; a non-zero exit has to be handled.

Your prior-art list is the strongest evidence in this thread: #81361 shows the same snapshot-at-boot mechanism surfacing on worktrees, so this isn't a memory bug but an inheritance-semantics gap — and your docs observation (explicit capture-time qualifiers for git status and the sibling roster, none for CLAUDE.md/memory) reads like the startup documentation grew item by item without a shared capture-time model.

On reference-with-generation-counter vs copy: reference fixes knowing, but adds a lifetime question copies don't have — who keeps the referenced store alive, and what does a subagent do when the ref dangles? A memory_loaded_at line plus disk-as-truth on arrival gets most of the visibility at a fraction of the semantic cost. Worth stating in the docs even before any wiring change: today the only way an agent learns its memory is a snapshot is us telling it.

shafee1913 · 2 days ago

Closing the loop on the exit-code point, with a field report from two days after my last comment.

Live occurrence, 26 Aug. Parent session booted 11:55. A rule in the memory directory was amended and committed around 16:30. A subagent spawned at 16:41 — six minutes later — woke holding the 11:55 snapshot: the old text of the amended rule, five hours stale, while the prompt that dispatched it truthfully described the new text. Nothing in the subagent's context marked the gap; the snapshot and the prompt simply disagreed, and the snapshot looked more authoritative (full file vs. paraphrase). The check-on-arrival step caught it — git log on the memory repo showed the amendment landing after the loaded copy's vintage — and the agent paused to ask rather than proceed on either version. So the convention held that day. Your objection stands anyway: a subagent that skips the step inherits the stale rule with full confidence, and we'd never know.

So we took your fix. The arrival check is now a script contract instead of prose:

  • 0 clean — snapshot age measured, nothing the agent woke holding has changed since
  • 1 blind — snapshot age unmeasurable; staleness reported as unknown, never as absent
  • 2 breach — loaded files changed since parent boot, or files the loaded index still names have been deleted from disk

The 1 case earned its place immediately: the boot-time probe has a fallback path, and before this change an agent on the fallback got the same reassuring output as a verified-fresh one. An instrument that can't tell "switched off" from "nothing happening" reports healthy forever — the exit code makes the two states distinguishable to the caller, and the final line names which state was seen so it survives into transcripts.

None of this touches the underlying issue: the snapshot semantics are still undocumented, and every mitigation in this thread is user-space scaffolding around a capture point the docs don't state. The ask remains documentation first — a timing qualifier on the CLAUDE.md/memory line of the startup list, matching the two the page already has.