[BUG] A subagent's compaction summary says "read the full transcript at:" the PARENT session's file, never the subagent's own agent-<id>.jsonl
**When a subagent auto-compacts, its summary says "read the full transcript at:" and gives the parent session's transcript. The subagent's own transcript is never named.**
Every compaction summary ends with:
If you need specific details from before compaction (like exact code snippets, error messages, or content you generated), read the full transcript at: <path>
In the main thread, <path> is the session's own ~/.claude/projects/<project>/<sessionId>.jsonl, which is correct. In a subagent it is the same path: the parent's file. The subagent's own turns are written to ~/.claude/projects/<project>/<sessionId>/subagents/agent-<agentId>.jsonl, and that path is never named. A compacted subagent that follows the instruction searches a conversation it was never part of. Its own reads, drafts and tool output are not in that file.
This is related to #91910, where hook payloads for a subagent's compaction have no agent identity. Both look like a subagent's compaction running under the parent's identity. But this is a different surface with a different fix site, so it's filed separately.
Reproduction (2.1.281, npm latest, Linux; also seen on 2.1.121 → 2.1.280)
Create filler files large enough that a subagent reading them passes a 100k auto-compact window:
mkdir repro && cd repro && git init -q
python3 - <<'EOF'
import random
random.seed(7)
w="alpha bravo charlie delta echo foxtrot golf hotel india juliet kilo lima mike november oscar papa quebec romeo sierra tango uniform victor whiskey xray yankee zulu".split()
for i in range(1,9):
with open(f"filler-{i}.txt","w") as f:
for n in range(1,1801):
f.write(f"{i}-{n}: "+" ".join(random.choice(w) for _ in range(14))+"\n")
EOF
CLAUDE_CODE_AUTO_COMPACT_WINDOW=100000 claude -p --model haiku --permission-mode bypassPermissions --output-format json \
"Use the Agent tool exactly once, subagent_type general-purpose, model haiku, with this prompt: 'In the current directory, use the Read tool to read filler-1.txt, filler-2.txt, filler-3.txt and filler-4.txt completely, each in three chunks: offset 1 limit 600, offset 601 limit 600, offset 1201 limit 600. Do all twelve reads in order, one per turn, without skipping. After the last read, reply with only the last line of filler-4.txt.' Then reply with the subagent's answer." > out.json
Then inspect the subagent's transcript, ~/.claude/projects/<project>/<sessionId>/subagents/agent-<id>.jsonl:
jq -r 'select(.subtype=="compact_boundary") | "BOUNDARY trigger=\(.compactMetadata.trigger) pre=\(.compactMetadata.preTokens) v=\(.version)"' agent-*.jsonl
jq -r 'select(.isCompactSummary==true) | (.message.content|if type=="string" then . else .[0].text end) | capture("read the full transcript at: (?<p>\\S+)").p' agent-*.jsonl
Observed (session 7b46f498-…, subagent agent-af0aa9c5d5bf61457):
BOUNDARY trigger=auto pre=68699 v=2.1.281
BOUNDARY trigger=auto pre=68088 v=2.1.281
BOUNDARY trigger=auto pre=68547 v=2.1.281
~/.claude/projects/<project>/7b46f498-f56f-460b-8bae-a90f83fc6c77.jsonl
~/.claude/projects/<project>/7b46f498-f56f-460b-8bae-a90f83fc6c77.jsonl
~/.claude/projects/<project>/7b46f498-f56f-460b-8bae-a90f83fc6c77.jsonl
What the two files contain:
7b46f498-f56f-460b-8bae-a90f83fc6c77.jsonl 40 lines, 0 Read calls <- the file every summary names (the parent)
agent-af0aa9c5d5bf61457.jsonl 112 lines, 9 Read calls <- the subagent's own turns (named by no summary)
Expected: each summary names ~/.claude/projects/<project>/7b46f498-…/subagents/agent-af0aa9c5d5bf61457.jsonl.
Actual: each names the parent's 7b46f498-….jsonl, which contains none of the subagent's reads or output.
How widespread
I scanned every transcript on one machine (sessions from June to September 2026):
| transcript kind | summaries with the pointer | names its own file | names another file |
|---|---|---|---|
| subagent (subagents/agent-*.jsonl) | 33 | 0 | 33, every one naming the parent's <sessionId>.jsonl |
| main session (sample of 200 files) | 253 | 204 | 49, all fork/resume copies: the summary's uuid is in the named file, so the pointer was right where the summary was first written |
Versions among the 33 subagent cases: 2.1.121, 2.1.163, 2.1.174, 2.1.175, 2.1.177, 2.1.178, 2.1.191, 2.1.193, 2.1.195, 2.1.272, 2.1.276, 2.1.280, 2.1.281. This is not a recent regression.
Why it matters
The pointer is the only recovery path a compacted subagent is given, and subagents that auto-compact are the long-running ones: planners, executors, reviewers. In a real case, a planner subagent compacted after writing 20 of 32 plan files. Its summary told it to continue "per the design recorded in the analysis", which is the analysis section removed before the summary is shown. A draft it had built and checked in a scratch file was never named. The file the pointer named had no trace of either. The agent rebuilt the design from scratch, and about half of its post-compaction tool calls were re-reads.
Where it comes from (2.1.281 bundle; minified names, for orientation only)
- The summary is built by
zG(summaryText, { …, transcriptPath: <X>, replBindingsKept: …G7t(toolState, agentId)… }). The template endsread the full transcript at: ${n.transcriptPath}. - The compaction call sites pass
transcriptPath: pd(). pd()isfunction pd(){let t=cv()??Sc(he());return o(t,\${Y()}.jsonl\)}, the session-level<projectDir>/<sessionId>.jsonl.- The same object literal already has
agentIdin scope (it is passed toG7t), and the agent transcript subdir is managed by the adjacentsetAgentTranscriptSubdir.
Suggested fix
When compacting a subagent, pass the path its own messages are written to, <projectDir>/<sessionId>/subagents/agent-<agentId>.jsonl, as transcriptPath. Keep pd() for the main thread. Dropping the pointer for subagents would remove their only recovery path. Naming both files would invite the subagent to search the parent's conversation as if it were its own.