You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
bug(session): infinite compaction loop when compression fails to reduce context #27924
This happens when compaction fails to reduce the context below the token limit — for example, when compression state is lost, messages are inherently uncompressable, or the compression plugin returns an error.
The loop spins indefinitely consuming API credits without making progress.
Steps to Reproduce
Have a session with large context that triggers overflow
If compaction fails or doesn't reduce tokens enough, the loop retries indefinitely
Observe continuous API calls with no progress
Root Cause
The runLoop function has two paths that trigger compaction and continue without any guard:
Overflow check: after each step, if compaction.isOverflow() returns true, it calls compaction.create() and continues — but the next iteration sees the same overflow and repeats.
Compact result: when the provider returns "compact" as the result, it calls compaction.create() and continues — same infinite loop if compaction doesn't actually reduce context.
Neither path has a retry counter or break condition.
Expected Behavior
After a reasonable number of compaction attempts (e.g., 2), the loop should break and return an error to the user, rather than spinning indefinitely.
If your issue is distinct from these (e.g., it specifically covers the case where compaction runs but doesn't reduce tokens below the limit, with no retry guard), please clarify the difference so maintainers can track it separately.
I’d make this a terminal state rather than another retry path. A useful regression test would force overflow, make compaction return the same-or-larger token count (or an error), then verify the runner:
attempts compaction at most N times,
records before/after token counts and the compaction reason,
stops further model calls with a state like compaction_failed / blocked_context_overflow,
preserves the last successful turn plus a recovery handoff, and
tells the user the exact next recovery action: export/clear/restart or remove the uncompressible payload.
Without the before/after token receipt, an infinite compact loop can look like progress while only spending credits.
Status: DRAFT — gated as comms-in-user-name. Do NOT post until user (dbednarc) reviews and approves. Target:#27924 Author identity if posted: dbednarc (Cisco CX EMEA) Date drafted: 2026-05-23
Suggested comment body
Confirming this on OpenCode 1.14.50 with Claude Opus 4.7 via GitHub Copilot, reasoning=medium. Reproduces deterministically as a compound of the runLoop defect you describe + a Copilot-provider context-budget mismatch.
Reproduction
Fresh session, cwd is a repo with a large AGENTS.md (~30K tokens) plus tool schemas (MCP servers loaded: tech-playbook, second-brain, salesforce-dx, quicker, delphi) — combined first-message overhead ~100K tokens.
Run an unremarkable orchestration task (in our case: a documentation-drift port across sister repos that completed cleanly at commit 52aedfd).
After agent reports "task complete", send "continue" or any short follow-up.
Spinner enters ▣ Compaction · Claude Opus 4.7 and never exits. Status bar shows "9.5K / 5% used" — i.e. the post-compaction count — yet compaction triggers again immediately on the next loop iteration.
Each cycle burns one Opus 4.7 request at Copilot's 7.5× premium rate, indefinitely, until the user kills the process.
Root-cause analysis (two compounding defects)
Defect 1 (this issue) — runLoop has no retry guard. As you noted, both the overflow path (compaction.isOverflow() → compaction.create() → continue) and the provider-returned "compact" result path call compaction.create() without any retry counter, break condition, or "did compaction actually reduce context below threshold?" check. If compaction's output token count is still ≥ the heuristic limit, the very next iteration sees the same overflow and re-fires. Pure infinite loop.
Defect 2 — Copilot provider context heuristic is too conservative for Claude Opus on Copilot. The GitHub Copilot provider appears to use a heuristic-backed default context limit instead of the actual 200K cap Copilot grants claude-opus-4.7. With our ~100K of system/tool/AGENTS.md overhead, the session looks already-over-budget against the heuristic before the user has typed anything — so Defect 1's loop fires on the first turn after any idle interval.
Defect 2 is workaroundable client-side (explicit provider.github-copilot.models.claude-opus-4.7.limit.context = 200000 in opencode.json) — but Defect 1 still bites whenever compaction genuinely fails to reduce context below threshold (e.g. when the in-context AGENTS.md + tool schemas alone exceed the post-compaction target). The loop should break after N consecutive non-reducing compactions, surface an error to the user, and let the user choose between "/clear", switch model, or manual context prune.
auto: false trades the loop risk for occasional hard-stops at context limit, which we'd rather handle manually than burn premium requests in a spinner.
Pre-post checklist (for user before posting)
Confirm OpenCode version still matches (opencode --version == 1.14.50)
Re-confirm Copilot Opus 4.7 still pinned to medium reasoning + 200K cap (no upstream tier change)
Sanity-check tone (factual, no Cisco-internal references, no customer names)
Decide whether to mention CX/Cisco context (recommendation: no — keep generic, posted as personal GitHub identity)
Optional: attach a redacted session log if reviewers ask (currently we do NOT have one — stuck instance was killed before transcript export)
Post via gh issue comment 27924 --repo sst/opencode --body-file ISSUE_COMMENT_DRAFT.md (strip the front-matter and the checklist before posting — only the "Suggested comment body" section above goes on the issue)
I can attest it's happening with more than just Copilot. It happens across various models on OpenCode Go and Ollama Cloud as well. I think it's a bigger issue than just one provider. But this right here "compaction": { "auto": false, "prune": true, "reserved": 10000 }, just helped dramatically.
I reproduced this on OpenCode 1.17.11 with a long-running session. The DB tail showed automatic overflow compaction ({"type":"compaction","auto":true,"overflow":true}) followed by a compaction assistant failure (MessageAbortedError), and resuming the same session re-entered agent=compaction mode=primary instead of stopping on the original overflow.
I opened a small PR focused on that recovery gap: if overflow recovery summarization aborts/dies with a non-interrupt failure, treat compaction as failed and persist the original provider overflow as the assistant error. Real user/session interrupts are still preserved.
Reproduced on 1.18.2 and dug into where the loop actually lives. One data point that may help scope the fix: the two open PRs (#34029, #34261) patch the core V2 runner (packages/core/src/session/runner/llm.ts and packages/core/src/session/compaction.ts), but the loop I captured runs through the session prompt path in packages/opencode/src/session/prompt.ts, which is what the server prompt handler wires up. Unless I am misreading the wiring, those PRs will not cover it.
Evidence from my local DB: one long-running opencode run session recorded 35 consecutive assistant messages with mode: "compaction", one every 30 to 90 seconds for over an hour. Every one of them finished stop with no error. So this is not only the summarizer-failure mode: each summary succeeded but did not shrink the request enough (we had compaction.prune: false, so giant old tool outputs stayed), the next turn overflowed, and compaction.create fired again with nothing bounding the cycle.
In SessionPrompt the mid-turn overflow path (result === "compact" -> compaction.create) has no attempt limit and no check that compaction reduced tokens, which is the same gap #34261 closes for the core runner.
What fixed it for us, in case it helps others:
compaction.prune: true so compaction can actually drop old tool outputs.
Pinning the built-in compaction agent to a large-context model so the summary prompt itself cannot overflow.
A small guard in the SessionPrompt loop: count consecutive overflow -> compaction cycles with no successful turn in between, and after 3, fail the message with ContextOverflowError instead of compacting again. Running this as a source patch since yesterday; the loop is gone and legitimate multi-compaction sessions still work because any productive turn resets the counter.
Happy to open a PR for the guard on the SessionPrompt path if maintainers want it, either standalone or rebased on top of #34261.
Deterministic fresh-session reproduction of this loop (OpenCode 1.18.18, OpenAI-compatible local vLLM endpoint):
Setup: a model entry with limit: {context: 65536, output: 32768} plus one MCP server exposing a large tool catalog (GitLab MCP, 135 tools). No conversation history — a brand-new session.
Repro:opencode run "print exactly: PROBE-OK" in an empty directory.
Result: the session immediately enters the compaction agent and streams compaction rounds forever (13 agent=compaction stream events in 240 s with --print-logs; single message=created, so this is not a continued session). The task never answers; the process never exits. Control: the identical config with the MCP server disabled completes in 7 s with zero compaction events.
Why it can never make progress: the compaction trigger sits at context − max(output, buffer) = 65536 − 32768 = 32768 tokens, and the fixed prompt overhead (135 tool definitions + system prompt + instruction files) appears to exceed that on its own — and compaction cannot compact away fixed overhead, so every round re-triggers. (In longer agentic runs on the same config the model's first turns sometimes do complete and write files before the hang, so the trigger arithmetic may not be the whole mechanism — but the MCP-catalog dependency is fully reproducible either way.)
This is a sharper trigger than the long-session framing above: no history needed, fails from message zero, bisectable by toggling one MCP server. Happy to provide the full probe logs. A guard like the one in #37584 (bounded consecutive compaction cycles → explicit ContextOverflowError) would have turned this hang into an actionable error — that PR was closed by the stale-bot with green checks; this repro may be a reason to revive it.
Thanks for the report! OpenCode 2.0 is now out and development is focused on V2, so we're closing issues filed against OpenCode 1. Please upgrade (opencode upgrade --major, details: https://anoma.ly/notes/opencode-2-0), and if this is still relevant on 2.0, reopen this issue or open a new one with your 2.0 version.
Activity
github-actions commentedon May 16, 2026
This issue might be a duplicate of existing issues. Please check:
If your issue is distinct from these (e.g., it specifically covers the case where compaction runs but doesn't reduce tokens below the limit, with no retry guard), please clarify the difference so maintainers can track it separately.
tommy29tmar commentedon May 17, 2026
I’d make this a terminal state rather than another retry path. A useful regression test would force overflow, make compaction return the same-or-larger token count (or an error), then verify the runner:
compaction_failed/blocked_context_overflow,Without the before/after token receipt, an infinite compact loop can look like progress while only spending credits.
dawidbednarczyk commentedon May 23, 2026
Draft comment for #27924
Status: DRAFT — gated as comms-in-user-name. Do NOT post until user (dbednarc) reviews and approves.
Target: #27924
Author identity if posted: dbednarc (Cisco CX EMEA)
Date drafted: 2026-05-23
Suggested comment body
Confirming this on OpenCode 1.14.50 with Claude Opus 4.7 via GitHub Copilot, reasoning=medium. Reproduces deterministically as a compound of the
runLoopdefect you describe + a Copilot-provider context-budget mismatch.Reproduction
AGENTS.md(~30K tokens) plus tool schemas (MCP servers loaded: tech-playbook, second-brain, salesforce-dx, quicker, delphi) — combined first-message overhead ~100K tokens.52aedfd).▣ Compaction · Claude Opus 4.7and never exits. Status bar shows "9.5K / 5% used" — i.e. the post-compaction count — yet compaction triggers again immediately on the next loop iteration.Root-cause analysis (two compounding defects)
Defect 1 (this issue) —
runLoophas no retry guard. As you noted, both the overflow path (compaction.isOverflow()→compaction.create()→continue) and the provider-returned"compact"result path callcompaction.create()without any retry counter, break condition, or "did compaction actually reduce context below threshold?" check. If compaction's output token count is still ≥ the heuristic limit, the very next iteration sees the same overflow and re-fires. Pure infinite loop.Defect 2 — Copilot provider context heuristic is too conservative for Claude Opus on Copilot. The GitHub Copilot provider appears to use a heuristic-backed default context limit instead of the actual 200K cap Copilot grants
claude-opus-4.7. With our ~100K of system/tool/AGENTS.md overhead, the session looks already-over-budget against the heuristic before the user has typed anything — so Defect 1's loop fires on the first turn after any idle interval.Defect 2 is workaroundable client-side (explicit
provider.github-copilot.models.claude-opus-4.7.limit.context = 200000inopencode.json) — but Defect 1 still bites whenever compaction genuinely fails to reduce context below threshold (e.g. when the in-context AGENTS.md + tool schemas alone exceed the post-compaction target). The loop should break after N consecutive non-reducing compactions, surface an error to the user, and let the user choose between "/clear", switch model, or manual context prune.Suggested guard (sketch)
Happy to test a fix against this repro if a PR lands.
Cross-references
tool_usewithouttool_result)(All of the above describe loop conditions that this issue's
runLoopguard would also cover.)Workaround we are running
~/.config/opencode/opencode.json:auto: falsetrades the loop risk for occasional hard-stops at context limit, which we'd rather handle manually than burn premium requests in a spinner.Pre-post checklist (for user before posting)
opencode --version== 1.14.50)gh issue comment 27924 --repo sst/opencode --body-file ISSUE_COMMENT_DRAFT.md(strip the front-matter and the checklist before posting — only the "Suggested comment body" section above goes on the issue)selfagency commentedon May 25, 2026
I can attest it's happening with more than just Copilot. It happens across various models on OpenCode Go and Ollama Cloud as well. I think it's a bigger issue than just one provider. But this right here
"compaction": { "auto": false, "prune": true, "reserved": 10000 },just helped dramatically.cwj2001 commentedon Jun 26, 2026
I reproduced this on OpenCode 1.17.11 with a long-running session. The DB tail showed automatic overflow compaction (
{"type":"compaction","auto":true,"overflow":true}) followed by a compaction assistant failure (MessageAbortedError), and resuming the same session re-enteredagent=compaction mode=primaryinstead of stopping on the original overflow.I opened a small PR focused on that recovery gap: if overflow recovery summarization aborts/dies with a non-interrupt failure, treat compaction as failed and persist the original provider overflow as the assistant error. Real user/session interrupts are still preserved.
a692570 commentedon Jul 18, 2026
Reproduced on 1.18.2 and dug into where the loop actually lives. One data point that may help scope the fix: the two open PRs (#34029, #34261) patch the core V2 runner (
packages/core/src/session/runner/llm.tsandpackages/core/src/session/compaction.ts), but the loop I captured runs through the session prompt path inpackages/opencode/src/session/prompt.ts, which is what the server prompt handler wires up. Unless I am misreading the wiring, those PRs will not cover it.Evidence from my local DB: one long-running
opencode runsession recorded 35 consecutive assistant messages withmode: "compaction", one every 30 to 90 seconds for over an hour. Every one of them finishedstopwith no error. So this is not only the summarizer-failure mode: each summary succeeded but did not shrink the request enough (we hadcompaction.prune: false, so giant old tool outputs stayed), the next turn overflowed, andcompaction.createfired again with nothing bounding the cycle.In
SessionPromptthe mid-turn overflow path (result === "compact"->compaction.create) has no attempt limit and no check that compaction reduced tokens, which is the same gap #34261 closes for the core runner.What fixed it for us, in case it helps others:
compaction.prune: trueso compaction can actually drop old tool outputs.compactionagent to a large-context model so the summary prompt itself cannot overflow.SessionPromptloop: count consecutive overflow -> compaction cycles with no successful turn in between, and after 3, fail the message withContextOverflowErrorinstead of compacting again. Running this as a source patch since yesterday; the loop is gone and legitimate multi-compaction sessions still work because any productive turn resets the counter.Happy to open a PR for the guard on the
SessionPromptpath if maintainers want it, either standalone or rebased on top of #34261.a692570 commentedon Jul 18, 2026
Opened #37584 with the
SessionPromptguard described above.lazebird commentedon Aug 12, 2026
like this?
YASoftwareDev commentedon Aug 19, 2026
Deterministic fresh-session reproduction of this loop (OpenCode 1.18.18, OpenAI-compatible local vLLM endpoint):
Setup: a model entry with
limit: {context: 65536, output: 32768}plus one MCP server exposing a large tool catalog (GitLab MCP, 135 tools). No conversation history — a brand-new session.Repro:
opencode run "print exactly: PROBE-OK"in an empty directory.Result: the session immediately enters the compaction agent and streams compaction rounds forever (13
agent=compactionstream events in 240 s with--print-logs; singlemessage=created, so this is not a continued session). The task never answers; the process never exits. Control: the identical config with the MCP server disabled completes in 7 s with zero compaction events.Why it can never make progress: the compaction trigger sits at
context − max(output, buffer)= 65536 − 32768 = 32768 tokens, and the fixed prompt overhead (135 tool definitions + system prompt + instruction files) appears to exceed that on its own — and compaction cannot compact away fixed overhead, so every round re-triggers. (In longer agentic runs on the same config the model's first turns sometimes do complete and write files before the hang, so the trigger arithmetic may not be the whole mechanism — but the MCP-catalog dependency is fully reproducible either way.)This is a sharper trigger than the long-session framing above: no history needed, fails from message zero, bisectable by toggling one MCP server. Happy to provide the full probe logs. A guard like the one in #37584 (bounded consecutive compaction cycles → explicit
ContextOverflowError) would have turned this hang into an actionable error — that PR was closed by the stale-bot with green checks; this repro may be a reason to revive it.Related: #22792, #27631 (large MCP tool sets + compaction, closed as duplicate).
thdxr commentedon Oct 11, 2026
Thanks for the report! OpenCode 2.0 is now out and development is focused on V2, so we're closing issues filed against OpenCode 1. Please upgrade (
opencode upgrade --major, details: https://anoma.ly/notes/opencode-2-0), and if this is still relevant on 2.0, reopen this issue or open a new one with your 2.0 version.— from 𝕺𝖕𝖊𝖓𝕮𝖔𝖉𝖊