Subagent task-output symlink mtime is stale — causes false 'stalled' diagnoses
Summary
When a background Agent is spawned, Claude Code places a file at:
<TMP>/claude/<project-hash>/tasks/<agent-id>.output
This path is a symlink pointing at the real transcript in:
~/.claude/projects/<project-hash>/<session-id>/subagents/agent-<agent-id>.jsonl
The symlink itself has an mtime frozen at symlink creation time. The target file is updated live as the agent works. Agents and users who check liveness via ls -la / stat on the symlink (without -L) see a frozen mtime and conclude the agent is stalled, when in fact it is working normally.
Impact
Agents running long, legitimate work (5–45 min) may be incorrectly killed by a supervising agent or user who polls the symlink's mtime. I killed a live, productive subagent based on this misdiagnosis; 45 minutes of work was discarded because the 'stalled' signal looked unambiguous.
Reproduction
- In a Claude Code session, launch a long-running background agent (e.g.,
Agent({ subagent_type: 'general-purpose', run_in_background: true, prompt: '...' })). Note the returnedagentId. - The response text names the output file:
output_file: <TMP>/claude/<hash>/tasks/<agent-id>.output. - Wait 10+ minutes while the agent works.
- Compare:
````
$ stat -c '%y' <TMP>/claude/<hash>/tasks/<agent-id>.output
2026-04-17 20:39:30
$ stat -L -c '%y' <TMP>/claude/<hash>/tasks/<agent-id>.output
2026-04-17 21:24:32
$ ls -la <TMP>/claude/<hash>/tasks/<agent-id>.output
lrwxrwxrwx ... -> /home/.../subagents/agent-<id>.jsonl
The unfollowed mtime says 45 min stale; the followed mtime is current.
- In my session I did
jq -r '.timestamp' agent-<id>.jsonland confirmed 399 messages distributed across the 45 min — max gap between tool-call messages was 302 s. The agent was actively making progress the entire time.
Why this is subtle
- Guidance in the tool result tells users: "Do NOT Read or Bash tail this file — it is the full sub-agent JSONL transcript". Users follow this — and thus rely on external signals like mtime to assess liveness.
- The output path is presented as if it were a normal file. The fact that it is a symlink is not surfaced.
ls -la,stat, and common scripting idioms all report the symlink's own mtime unless-Lis specified. This is standard POSIX behavior, not a bug in those tools.- Users don't see the
~/.claude/projects/.../subagents/directory in any tool result; they have to discover it by reading the symlink target.
Suggested fixes (any one is sufficient)
- Write the transcript file directly at the
<TMP>/.../tasks/<agent-id>.outputpath, or have the harnessutime()the symlink on each transcript update so its mtime tracks the target. (The symlink approach only makes sense if it's maintained.) - Expose an
AgentStatustool that returns{status, last_activity_ts, message_count}for a givenagent_idso supervising agents have a reliable liveness signal without file-system probing. - Rewrite the tool-result hint to explicitly warn: "To check liveness, use \
stat -L -c %y <path>\orAgentStatus(agent_id)— the symlink's own mtime is not a liveness indicator."
Environment
- Claude Code CLI running on Windows 10 (MSYS2 bash / git-bash)
- Claude Code version:
claude-opus-4-7[1m]model, current release - Two subagents spawned concurrently; confirmed behavior on both.
Evidence
Full transcripts are at agent-af381eba8b6dd2535.jsonl (45 min, killed prematurely) and agent-a98281f14b92904c9.jsonl (40 min, completed normally, delivered PR FastLED/fbuild#90). Both show steady tool-call activity in the .jsonl with stat -L mtimes current to within seconds, while stat (no -L) reports mtimes from spawn time.
Thanks — happy to share the transcripts if useful.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗