Skip to content

compaction.prune never trims context in single-turn opencode run sessions, so long headless runs compact every ~10 minutes #47485

Closed as not planned

Description

@Raviu56

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):

  1. prune skips 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.
  2. Even when it is eligible, prune is 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.
  3. 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.variant in 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 prune at 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

  1. Config: "compaction": { "prune": true }, provider openai with ChatGPT OAuth, model gpt-5.6 with variant max.
  2. 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).
  3. Watch the log: message=pruning never appears while the run is going, only message=found pruned=0 total=0 after it ends. In the DB no tool part ever gets state.time.compacted set.
  4. 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

  1. github-actions commented on Sep 5, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of an existing issue for one of the reported root causes. Please check:

    The other two root causes (the turns < 2 guard making prune a no-op for single-turn sessions, and prune only being called after the prompt loop exits) appear to be distinct and not yet filed — those are the novel parts of this report.

  2. darzanmihai commented on Sep 6, 2026

    @darzanmihai

    Independent repro from a different entry point: the HTTP server API (POST /session/{id}/prompt), not opencode run.

    We drive opencode headlessly through opencode serve with 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 with OpenAI Responses stream error. That lines up with the turns < 2 guard you pinned in prune: a /session/{id}/prompt call is a single user message, structurally identical to a single-turn opencode run, so nothing ever becomes eligible.

    A few extra datapoints:

    • Reproduced on both 1.18.4 and 1.18.29 (you checked 1.18.27 + dev), so it is not fixed in 1.18.29.
    • POST /session/{id}/compact returns Session compact is not available yet on 1.18.29, so manual compaction is not an over-the-API workaround either.
    • The only lever that helped from our side was shrinking our own tool-output payloads (one MCP tool went 820KB to 20KB). That let a build run further before poisoning, but it only delays the ceiling rather than shedding context, which is consistent with prune never running.

    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.

  3. neriousy commented on Oct 20, 2026

    @neriousy
    Member

    Thanks for the detailed root-cause analysis and the measurements. This is about the v1 compaction/prune code (opencode-ai 1.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 no compaction.prune pass 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions