[BUG] Plugin-spawned fork ($.agent.spawn, subagentType "fork") never reuses the parent's prompt cache; re-writes the whole history per fork

Status Open
Reported on v2.1.295
Maintainer reply None cached
Activity 0 comments · opened Oct 9, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

A fork subagent started from a plugin hook via $.agent.spawn reads only the cross-session static prefix (tools + system prompt) and re-writes the entire conversation as new cache. A fork started by the model itself through the Agent tool mid-turn reuses the parent's cached prefix exactly and writes only its tail. The plugin path therefore costs a full context write per fork instead of a few thousand tokens.

Clean reproduction, ~42k context, one session, no plugin reload:

| Request | cache_read | cache_creation | TTL |
|---|---|---|---|
| Main turn 1 | 13,512 | 28,905 | 1h |
| Plugin fork, 22 s later | 13,512 | 29,251 | 5m |
| Main turn 2 | 42,417 | 11 | 1h |

13,512 is the tools + system prompt prefix shared across sessions; the fork matched nothing past it. The parent's own cache was not affected (turn 2 hit). Same pattern on a ~257k context earlier that day: plugin fork read 15,371 / wrote 195,239 at 5m.

Control in the same session: the main model called Agent({ subagent_type: "fork", prompt: "reply ok" }) mid-turn. Parent request total 337,629; fork read 337,596 / wrote 4,247 at 5m. That is the expected behaviour.

What appears to differ: the tool-use context handed to a plugin's tool call is built from the session's transcript row list rather than the query's rendered message array (the fork's fork-context-ref row records contextLength equal to the transcript row count, 24 in the clean repro, not the message count). The fork renders that history itself, so its bytes diverge from the parent's at the first message; its first request also appends a fresh date attachment, i.e. it did not find the parent's date attachment in the messages it was given, while the Agent-tool fork added none. $.model.fork reuses the recorded main-thread request parameters and is cache-safe but has no tools; $.agent.spawn has no option to request the same.

What Should Happen?

A plugin-spawned fork sends the same bytes as the parent's last request (same system prompt, same rendered messages, same user/system context), so its first request reads the parent's prefix and writes only its own tail at the 5m TTL, as the Agent-tool fork and $.model.fork already do. Either take the recorded main-thread parameters on plugin-steered fork spawns, or expose an option on $.agent.spawn to request that.

Error Messages/Logs

Steps to Reproduce

Project plugin hooks module, loaded at session start (no reload during the test):

on('session.start', ($, e, next) => ($.command.register({ name: 'sq', argumentHint: '[question]', immediate: true }), next(e)))
on('command.run', { command: 'sq' }, async ($, e) => {
  await $.agent.spawn({ prompt: e.args, subagentType: 'fork', description: 'side quest' })
  return {}
})
on('turn.complete', { turnId: /^/ }, async ($, e, next) => {
  const r = await next(e)
  if (e.agentId) $.ui.log(`fork read=${e.usage?.cache_read_input_tokens} wrote=${e.usage?.cache_creation_input_tokens}`)
  return r
})
  1. Start a fresh session and send one main-thread prompt (reply with ok).
  2. While idle, run /sq what was the last thing I asked you?.
  3. Send one more main-thread prompt.
  4. Compare cache_read_input_tokens / cache_creation_input_tokens of the fork's first request with the parent's last request (transcript usage fields, or the logged line).

Claude Model

Other

Is this a regression?

No, this never worked

Last Working Version

_No response_

Claude Code Version

2.1.295

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

Terminal.app (macOS)

Additional Information

_No response_

View original on GitHub ↗