Skip to content

fix(opencode): inject the V2 bootstrap on compaction and generate requests - #2601

Open
obra wants to merge 1 commit into
devfrom
fix/2391-opencode-compaction-bootstrap
Open

obra wants to merge 1 commit into
devfrom
fix/2391-opencode-compaction-bootstrap

Conversation

@obra

@obra obra commented Oct 22, 2026 •

Copy link
Copy Markdown
Owner

Who is submitting this PR? (required)

Field Value
Your model + version Claude Opus 5.5 (claude-opus-5-5)
Harness + version Claude Code 2.1.308 (orchestrating session plus a worktree subagent)
All plugins installed claude-code-setup, claude-session-driver, deal-team, episodic-memory, frontend-design, github-triage, gopls-lsp, landing-page-design, plugin-dev, primeradiant-ops, proving-it-works, scenario-testing, simmer, simplify-code, summarize-meetings, superpowers (7.0.0), superpowers-chrome, superpowers-lab, worldview-synthesis
Human partner who reviewed this diff Jesse (@obra), pending

What your human partner asked you to do, in their words:

2197: repro and fix. [...] 2391: we do want a fix. our own fix that targets dev.

The orchestrating Claude Code session turned that into a brief for a subagent working in its own git worktree, using real OpenCode V1/V2 binaries and a local stub provider. The subagent reproduced, fixed and drafted this description. An independent code-review subagent then reviewed the branch before it was opened.

Human-authored summary (required, under 200 words)

Pending: Jesse (@obra) will write this section before the PR is reviewed.

What problem are you trying to solve?

#2391 (@tpischke): on OpenCode 2, the compaction request's first user message lacks the bootstrap that every normal turn carries. The prompt diverges right after system + tools, so prefix caching misses on the largest request of the session (the reporter measured 8,192 of 199,112 tokens cached and a 510 s prefill). I reproduced it on real OpenCode 2.1.2 and 2.1.3 by driving a session into automatic compaction with a scripted stub model. Before the fix, the compaction request shares 26.5k of 30.8k serialized chars with the previous agent request and differs at the first user message.

What does this PR change?

Registers the V2 bootstrap injector on the compaction and generate session hooks as well as context, with the handler body unchanged. Adds a hook-registration test and updates the two docs that named only context.

Is this change appropriate for the core library?

Yes. It's a cost and latency bug in the OpenCode V2 integration core ships, and it hits every V2 user whose session compacts.

What alternatives did you consider?

  • compaction only (the reporter's patch): this leaves generate, which is built from the same session transcript (session/generate.ts), with the same prefix break. Upstream's built-ins and migration guide register all three.
  • Also registering title: title requests build their own prompt and shouldn't carry the bootstrap.

Does this PR contain multiple unrelated changes?

No.

Existing PRs

Environments tested

Harness (e.g. Claude Code, Cursor) Harness version Model Model version/ID
OpenCode V2 (@opencode/cli) 2.1.2, 2.1.3 scripted stub provider (forces automatic compaction, records requests) n/a

New harness support (required if this PR adds a new harness)

Not applicable. This PR adds no harness.

Evaluation

  • Initial prompt: Probe the project files, with a stub model that reports 95k prompt tokens on a declared 100k window, so the host compacts automatically.
  • Sessions after the change: 2 real-host sessions (2.1.2, 2.1.3). The same 2 ran before.
  • Outcome: before, the compaction request had no bootstrap and diverged at the first user message (shared prefix 26.5k of 30.8k chars). After, it carries the bootstrap, and the whole previous agent request is a prefix of the compaction request.

Rigor

  • If this is a skills change: I used superpowers:writing-skills and completed adversarial pressure testing (not a skills change)
  • This change was tested adversarially, not just on the happy path (real compaction on two host versions; unit coverage for child sessions and repeated delivery on every hook)
  • I did not modify carefully-tuned content (Red Flags table, rationalizations, "human partner" language) without extensive evals showing the change is an improvement

Human review

  • A human has reviewed the COMPLETE proposed diff before submission

Merge note: conflicts with #2600

#2600 (#2197) and this PR both touch the V2 bootstrap guard. Whichever lands second hits one conflict hunk inside the V2 hook body. Resolve it by taking this PR's hunk and replacing p.text.includes('EXTREMELY_IMPORTANT') with #2600's p.text.startsWith(BOOTSTRAP_MARKER). The V1 guard and the constant merge cleanly. A trial merge resolved this way passes tests/opencode/run-tests.sh (5/5). Landing both matters: once compaction carries the bootstrap, a summary that quotes EXTREMELY_IMPORTANT would trip the old substring guard.

https://claude.ai/code/session_01SHANcr4A71nzuU9et4Fnba

…uests

The V2 plugin registered its bootstrap injector only on
ctx.session.hook('context'). OpenCode 2 prepares each model request kind
through its own hook: normal agent turns through `context`, the
compaction (summary) request through `compaction`, and session-scoped
one-shot generation through `generate`
(packages/core/src/session/model-request.ts). Every built-in V2 plugin
registers its handler on all three, and the upstream V1 migration guide
says to do the same when a transform must apply to every request kind.

So the compaction request's first user message lacked the bootstrap
that every prior turn carried, and the prompt diverged right after
system + tools. On a provider with prefix caching that is a cold
re-prefill of the whole session on its largest request (#2391).

Reproduced on real OpenCode 2.1.2 and 2.1.3 with a scripted stub model
that pushes the session over the compaction ceiling: before, the
compaction request shares 26.5k of 30.8k serialized chars with the
previous agent request and differs at the first user message; after,
the previous request is a full prefix of the compaction request.

The handler body is unchanged (child-session skip, idempotence guard,
checkpoint-only append), lifted into a named function and registered on
all three hooks, each in its own try/catch. Based on PR #2409.

Fixes #2391

Co-authored-by: Aashish <145881415+aashish254@users.noreply.github.com>
Claude-Session: https://claude.ai/code/session_01SHANcr4A71nzuU9et4Fnba
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant