Description
Description
Long headless runs (opencode run --format json, one user turn, several task subagents, OpenAI gpt-5.6 via ChatGPT OAuth, variant max) hit auto-compaction roughly every 10 minutes for hours. Each compaction is a 45-85k token summary call, the post-compaction context is only 22-42k, and the agent then re-reads the same files it just lost (one .cpp file was read 66 times in one session). It is not a hang, but it burns the account limit about 10x faster than it should and looks like a loop from the outside.
Root cause, in packages/opencode/src/session/compaction.ts and prompt.ts (checked on 1.18.27 and current dev):
pruneskips every message until it has seen two user messages (if (turns < 2) continue, compaction.ts ~line 286-292). A headless run has exactly one user message, so nothing is ever eligible.- Even when it is eligible,
pruneis only called after the prompt loop exits (prompt.ts ~line 1338, forked into scope). In a long single-turn run the loop does not exit for hours, so the live context is never trimmed while it fills up. - The compaction summary message inherits the user's variant (compaction.ts ~line 400:
variant: userMessage.model.variant). With variant max the summary carries 26-94 encrypted reasoning items that are replayed on every later request at roughly 170 tokens each, which is a big part of why the post-compaction base is so large. There is no way to run the compaction agent at a cheaper variant;agent.compaction.variantin config is ignored.
Setting compaction.prune: true in config therefore has no effect for opencode run, and preserve_recent_tokens only changes what survives a compaction, not how fast the window fills.
I have a fix running locally (two commits on top of dev, tests in test/session/compaction.test.ts pass, typecheck clean):
- call
pruneat the top of each loop iteration after the first step, so it runs while the session is live - protect by token budget only (newest PRUNE_PROTECT tokens of tool output kept verbatim, older cleared once more than PRUNE_MINIMUM is reclaimable), drop the two-turn guard
- let the compaction agent use its own configured variant if the model has it, otherwise fall back to the user's
Measured on the same workload with the patched build: tool output in the prompt stays in a 50-59k band, ingestion dropped from about 22k to 3.8k tokens per minute, zero compactions in the window where the stock build did two, none of the 30 cleared outputs had to be re-fetched. The one cost is that the step right after a clearing loses its prompt-cache prefix, which is why the 20k minimum matters.
Plugins
@slkiser/opencode-quota (display only). OpenAI provider via the built-in ChatGPT OAuth login.
OpenCode version
1.18.27 release binary; same code on dev at e289456
Steps to reproduce
- Config: "compaction": { "prune": true }, provider openai with ChatGPT OAuth, model gpt-5.6 with variant max.
- Give a large repo a task that needs a lot of reading, e.g. "audit every file under src/ and write a report", and run it headless: opencode run --format json "" (optionally with subagents via the task tool).
- Watch the log:
message=pruningnever appears while the run is going, onlymessage=found pruned=0 total=0after it ends. In the DB no tool part ever gets state.time.compacted set. - Watch step-finish tokens.total in the JSON output: it climbs to the threshold (272k minus 20k reserve for this model) and a compaction part appears about every 10 minutes for the whole run, with the same files being read again after each one.
Screenshot and/or share link
No share link (private code). Can attach log excerpts or the DB query I used on request.
Operating System
Debian 13 (trixie), Linux 6.12
Terminal
tmux inside a Linux terminal; not terminal related, the run is headless
Activity
github-actions commentedon Sep 5, 2026
This issue might be a duplicate of an existing issue for one of the reported root causes. Please check:
agent.compaction.variantconfig being ignored (your point 3 — compaction summary inheriting the user's variant instead of the compaction agent's configured variant) is already tracked there.The other two root causes (the
turns < 2guard makingprunea no-op for single-turn sessions, andpruneonly being called after the prompt loop exits) appear to be distinct and not yet filed — those are the novel parts of this report.darzanmihai commentedon Sep 6, 2026
Independent repro from a different entry point: the HTTP server API (
POST /session/{id}/prompt), notopencode run.We drive opencode headlessly through
opencode servewith one user message per prompt call,openai/gpt-5.2, on a long tool-heavy build (dozens of large MCP tool outputs per turn). Same symptom you describe: context is never pruned, and the session eventually poisons around 50 large messages withOpenAI Responses stream error. That lines up with theturns < 2guard you pinned inprune: a/session/{id}/promptcall is a single user message, structurally identical to a single-turnopencode run, so nothing ever becomes eligible.A few extra datapoints:
POST /session/{id}/compactreturnsSession compact is not available yeton 1.18.29, so manual compaction is not an over-the-API workaround either.Net: the guard is not specific to
opencode run --format json. Any single-user-turn flow through the server API trips it the same way.neriousy commentedon Oct 20, 2026
Thanks for the detailed root-cause analysis and the measurements. This is about the v1 compaction/prune code (
opencode-ai1.18.x), which now only gets critical fixes, so it won't be changed there. v2 (@opencode/cli) has a rewritten compaction system: there is nocompaction.prunepass anymore, and the summary transcript leaves out reasoning. If long headless runs on v2 still compact far more often than expected, comment with your version, model and a log excerpt and we'll reopen.