Keep long OpenCode sessions alive. When an assembled model request would overflow the model's context window, this trims it before dispatch.
OpenCode already compacts long sessions. But a request can be too large for reasons compaction does not cover — a huge pasted file, an imported transcript, a dozen inline screenshots.
When that happens the provider rejects the call:
the model's provider rejected the request.
check the model id, request fields, and context length
The part that makes this fatal: the same oversized transcript is what compaction would summarize. So compaction fails too, and the session is stuck. There is no prompt you can send that recovers it, because every request carries the same oversized history.
This plugin breaks the deadlock by shrinking the outgoing request.
Registers on two hooks, context (the agent loop) and compaction (checkpoint
summaries) — both, or a stuck session stays stuck.
When a request exceeds a budget derived from the model's context window, it shrinks the largest offenders first:
- Inline attachments (
fileparts holdingdata:URIs, base64 image parts) are replaced by a short text placeholder. - Long strings are truncated to a prefix, with a note recording how much was dropped.
It rewrites the outgoing request only. Persisted history is never touched, so nothing is lost — the session keeps working and the full data stays on disk.
opencode plugin add github:mimu-sh/opencode-context-guardOr add it to ~/.config/opencode/opencode.json:
To configure options, use the object form:
{
"plugins": [
{
"package": "github:mimu-sh/opencode-context-guard",
"options": {
"targetRatio": 0.75,
"verbose": true
}
}
]
}Reload after changing configuration:
opencode reload| Option | Default | Meaning |
|---|---|---|
enabled |
true |
Master switch. |
targetRatio |
0.75 |
Aim for at most this fraction of the context window. |
contextLimit |
1000000 |
Fallback window when the model reports none. |
contextLimitOverride |
— | Force a window for every model, skipping discovery. |
minString |
8000 |
Only strings longer than this are trimmed. |
keepString |
2500 |
How many leading characters of a trimmed string survive. |
charsPerToken |
2.2 |
Conservative chars-per-token estimate. |
logFile |
— | Append JSON-lines diagnostics here. |
verbose |
false |
Also write diagnostics to stderr. |
- It does not raise your context window. It makes oversized requests fit; it cannot make the model remember more.
- It is lossy by design. Trimmed content is gone from that request. The original stays in the session history, so a later compaction or a fresh session can still recover it — but the current turn will not see it.
- It does not replace compaction. Compaction summarizes; this only shrinks. Run with compaction enabled.
- It guesses token counts. Providers do not agree on tokenization, so the
estimate is deliberately conservative. A request under budget can still be
rejected by a provider with a stricter limit; raise
targetRatiosafety by lowering it.
The bug that motivated it is in how a request gets assembled, not in OpenCode's compaction policy. A plugin can rewrite the outgoing request without changing persisted state, which is the right blast radius for a workaround. If OpenCode grows a first-class answer, this should be deleted.
npm install
npm test # unit tests for the pruning logicNo build step. index.js and prune.js are the shipped artifacts, in plain ESM,
so what you test is what runs. prune.js has no OpenCode imports, so the logic
is tested directly; index.js is the plugin shell.
An earlier revision compiled from TypeScript. That was removed deliberately: a
build step adds a way for an install to half-work (a stale or missing dist/),
and a plugin that silently fails to load is worse than one that is slightly
plainer to read. The JSDoc annotations in prune.js carry the same type
information for editors without a compiler in the install path.
MIT
{ "plugins": ["github:mimu-sh/opencode-context-guard"] }