Short description
inputstring values to **5 characters + a...[truncated]` sentinel
What happened?
Package: @cortexkit/opencode-magic-context
Severity: High — produces corrupted, persisted tool records and real side-effects (files created at wrong paths, failed tool calls, corrupted long-term memories).
Environment
| Component |
Version |
@cortexkit/opencode-magic-context |
0.41.4 (= npm latest at time of report) |
| opencode (host) |
1.18.29 |
| Node.js |
v22.22.2 |
| OS |
Linux |
| Plugin peer dep |
@opencode-ai/plugin >= 1.15.0 |
Plugin install location (npx/opencode package cache):
~/.cache/opencode/packages/@cortexkit/opencode-magic-context@0.41.4/node_modules/@cortexkit/opencode-magic-context
Summary
In the context-compaction "truncated" drop mode, the plugin shrinks a tool call's input string values to 5 characters + a ...[truncated] sentinel (and sets output to a [dropped §N§] sentinel) in the LLM-facing transcript.
The problem: the truncated input looks like real but mangled data (e.g. "command":"docke...[truncated]", "action":"writ...[truncated]"), not like an explicit "this was dropped, recover it" placeholder. When the model later wants to repeat or continue that action, it copies the truncated values into a brand-new tool call. That new call is a normal live tool part, so it gets persisted to opencode's session DB with the corrupted input — and it actually executes with the corrupted values, producing real side effects.
This is a data-integrity defect: the model is induced to emit tool calls whose inputs are not what the user/intent asked for.
Root cause (with code references)
All references are to the shipped build (0.41.4): dist/index-0y2mcg5y.js and dist/index.js.
-
Sentinel + input truncation. TRUNCATION_SENTINEL2 = "...[truncated]" (index-0y2mcg5y.js:9547). truncateInputValues (index-0y2mcg5y.js:9557) rewrites every top-level string value of a tool input in place:
function truncateInputValues(input) {
for (const key of Object.keys(input)) {
const value = input[key];
if (typeof value === "string") {
if (value.endsWith(TRUNCATION_SENTINEL2) || value === "[object]" || /^\[\d+ items\]$/.test(value))
continue;
input[key] = value.length > 5 ? `${safeSlice2(value, 5)}${TRUNCATION_SENTINEL2}` : value;
} else if (Array.isArray(value)) {
input[key] = `[${value.length} items]`;
} else if (value !== null && typeof value === "object") {
input[key] = "[object]";
}
}
}
Note input[key] = ... — this mutates the input object in place (first 5 chars + ...[truncated]). Arrays become [N items], objects become [object].
-
Applied when a tool part is dropped in "truncated" mode. truncateToolPart (index-0y2mcg5y.js:9473) sets state.output = "[dropped §N§]" and calls truncateInputValues(state.input) (when input size > 500). It is invoked per occurrence from createToolDropTarget().truncate() (index-0y2mcg5y.js:9672):
const truncate = () => {
const entry = index.get(compositeKey);
if (!entry || entry.occurrences.length === 0) return "absent";
if (!entry.hasResult) return "incomplete";
for (const occurrence of entry.occurrences) {
clampCloneInPlace(occurrence, (part) => truncateToolPart(part, tagId));
}
clearThinkingParts(thinkingParts);
return "truncated";
};
-
Runs on the live transcript. The whole transform is wired to the experimental.chat.messages.transform hook (index.js:38968), i.e. it operates on the live message objects shown to the model.
Key distinction (verified empirically)
The plugin's in-place mutation is NOT persisted to opencode's DB. Scanning the full opencode.db (all part rows of type:"tool"):
- 0 parts have
state.output === "[dropped §N§]".
- The persisted corrupted parts all have real tool outputs (the tool genuinely ran), never the drop sentinel.
So opencode stores the original full input/output. The only path by which truncated values reach the DB is the model re-issuing a new tool call using the truncated values it sees in the transcript. The defect is therefore not "the plugin writes bad data to the DB" — it is "the plugin presents inputs in a form that reliably leads the model to emit new, corrupted tool calls."
Evidence (from a full scan of ~/.local/share/opencode/opencode.db)
Signature used: a top-level tool-input string value equal to exactly 5 chars + ...[truncated] (the truncateInputValues shape).
30 tool parts match, by tool:
| tool |
count |
ctx_memory |
12 |
bash |
7 |
paseo_create_agent |
5 |
edit |
3 |
write |
2 |
task |
1 |
9 of the 30 have status: "error". Time window 2026-09-02 → 2026-09-07.
Concrete persisted records (truncated input → real output proving it executed):
- bash —
input: {"command":"whic...[truncated]"} → output /usr/bin/bash: whic...[truncated]: command not found. (Also echo..., slee..., true..., docke..., chrom... variants.)
- ctx_memory —
input: {"action":"writ...[truncated]","category":"CONFI...[truncated]","content":"YouTr...[truncated]"} → output Error: Action 'writ...[truncated]' is not allowed.
- edit — both
oldString and newString truncated to the same 5-char prefix → 43 Error: No changes to apply: oldString and newString are identical (38 edit, 5 bash).
- write —
input.filePath truncated to /tmp/...[truncated] while content was full → a real 2203-byte file was created at the literal path /tmp/...[truncated].
- ctx_memory (succeeded) —
input: {"action":"write","category":"ARCHITECTURE","content":"<5 chars>...[truncated]"} (18-char content) → output Saved memory [ID: 439] in ARCHITECTURE. — this created a corrupted long-term memory (content = 5 chars + sentinel). Several cross-session memories were created this way (5-char/sentinel content), i.e. the defect corrupts not just the session DB but the plugin's own durable memory store.
Corroborating (not counted above): ~113 parts contain ...[truncated] in their output — that is opencode's own large-output truncation, unrelated to input corruption, and should not be conflated with this bug.
Impact
- Corrupted persisted tool records in opencode's session DB (inputs are 5-char/sentinel, not the real command/arguments).
- Real side effects from executing garbage inputs: shell "command not found", file created at a wrong/truncated path, no-op edits ("identical oldString/newString").
- Corrupted long-term memories: re-issued
ctx_memory write calls with truncated content pass validation (valid action/category) and persist 5-char/sentinel memories, degrading future-session knowledge.
- Wasted agent turns: the model retries the same broken call (e.g. repeated
docke...[truncated]), compounding context and cost.
- Irreversible: the original inputs of the re-issued calls are not recoverable (the plugin's store keeps user messages, not tool inputs).
Suggested fix
The core issue is the representation of a dropped input. Options (any of which breaks the "model copies it" behavior):
- Do not render dropped inputs as mangled real data. Replace the input with an explicit, non-copyable marker that also names the recovery path, e.g.:
[input dropped — original not shown; recover via ctx_expand(message=<N>)]
rather than docke...[truncated]. The current ctx_expand/ctx_search recovery mechanism already exists; the dropped input should point to it instead of inviting a verbatim copy.
- Guard the model's re-issue. When a new tool call's input matches the truncation signature (5 chars + sentinel /
[N items] / [object]), reject or warn instead of executing (a tool.execute.before-style guard), since such an input is always a transcription of a dropped value, never a genuine user intent.
- Truncate only the output, keep the input intact for tool parts that are likely to be re-issued (edits, writes, memory writes), or at minimum keep the full input available in the transcript for the turns immediately following the drop.
A minimal, safe change is option 1 (change the dropped-input rendering) — it removes the "looks like real data" property that triggers the copy behavior.
Reproduction (characteristic, not deterministic)
The trigger is model-dependent, so this is a characteristic reproduction rather than a unit test:
- Run a long opencode session under the magic-context plugin until context pressure causes the plugin to mark completed tool parts for drop in
truncated mode (the mode that applies truncateToolPart).
- Observe the LLM-facing transcript: those tool calls now show
input values as xxxxx...[truncated] and output as [dropped §N§].
- On a later turn where the model needs to repeat/continue one of those actions (retry a failed command, re-save a memory, redo an edit), it re-issues the call copying the truncated input.
- The new call executes and persists with the truncated input → corrupted DB record + side effect (e.g.
command not found, oldString and newString are identical, file at a truncated path).
A deterministic assertion for a regression test: after a drop in truncated mode, no subsequently-persisted tool part's input may contain the ...[truncated] sentinel / [N items] / [object] markers.
Notes / scope
- The plugin is already at the latest published version (0.41.4); there is no newer release with a fix.
- There is currently no config option to disable input-value truncation in the drop mode (
compaction in config controls the host's built-in compaction, which the plugin disables — it does not gate this behavior).
- The in-memory mutation itself is benign to the DB (verified: 0 persisted
[dropped §N§] outputs); the bug is specifically the downstream model re-issue it induces.
Diagnostics
Plugin version
0.41.4
OpenCode version
1.18.29
Platform
linux x64
Client
OpenCode Web
Log output (optional)
https://github.com/cortexkit/magic-context/issues/250
Short description
input
string values to **5 characters + a...[truncated]` sentinelWhat happened?
Package:
@cortexkit/opencode-magic-contextSeverity: High — produces corrupted, persisted tool records and real side-effects (files created at wrong paths, failed tool calls, corrupted long-term memories).
Environment
@cortexkit/opencode-magic-contextlatestat time of report)@opencode-ai/plugin >= 1.15.0Plugin install location (npx/opencode package cache):
~/.cache/opencode/packages/@cortexkit/opencode-magic-context@0.41.4/node_modules/@cortexkit/opencode-magic-contextSummary
In the context-compaction "truncated" drop mode, the plugin shrinks a tool call's
inputstring values to 5 characters + a...[truncated]sentinel (and setsoutputto a[dropped §N§]sentinel) in the LLM-facing transcript.The problem: the truncated input looks like real but mangled data (e.g.
"command":"docke...[truncated]","action":"writ...[truncated]"), not like an explicit "this was dropped, recover it" placeholder. When the model later wants to repeat or continue that action, it copies the truncated values into a brand-new tool call. That new call is a normal live tool part, so it gets persisted to opencode's session DB with the corrupted input — and it actually executes with the corrupted values, producing real side effects.This is a data-integrity defect: the model is induced to emit tool calls whose inputs are not what the user/intent asked for.
Root cause (with code references)
All references are to the shipped build (0.41.4):
dist/index-0y2mcg5y.jsanddist/index.js.Sentinel + input truncation.
TRUNCATION_SENTINEL2 = "...[truncated]"(index-0y2mcg5y.js:9547).truncateInputValues(index-0y2mcg5y.js:9557) rewrites every top-level string value of a tool input in place:Note
input[key] = ...— this mutates the input object in place (first 5 chars +...[truncated]). Arrays become[N items], objects become[object].Applied when a tool part is dropped in "truncated" mode.
truncateToolPart(index-0y2mcg5y.js:9473) setsstate.output = "[dropped §N§]"and callstruncateInputValues(state.input)(when input size > 500). It is invoked per occurrence fromcreateToolDropTarget().truncate()(index-0y2mcg5y.js:9672):Runs on the live transcript. The whole transform is wired to the
experimental.chat.messages.transformhook (index.js:38968), i.e. it operates on the live message objects shown to the model.Key distinction (verified empirically)
The plugin's in-place mutation is NOT persisted to opencode's DB. Scanning the full
opencode.db(allpartrows oftype:"tool"):state.output === "[dropped §N§]".So opencode stores the original full input/output. The only path by which truncated values reach the DB is the model re-issuing a new tool call using the truncated values it sees in the transcript. The defect is therefore not "the plugin writes bad data to the DB" — it is "the plugin presents inputs in a form that reliably leads the model to emit new, corrupted tool calls."
Evidence (from a full scan of
~/.local/share/opencode/opencode.db)Signature used: a top-level tool-input string value equal to exactly 5 chars +
...[truncated](thetruncateInputValuesshape).30 tool parts match, by tool:
ctx_memorybashpaseo_create_agenteditwritetask9 of the 30 have
status: "error". Time window 2026-09-02 → 2026-09-07.Concrete persisted records (truncated input → real output proving it executed):
input: {"command":"whic...[truncated]"}→ output/usr/bin/bash: whic...[truncated]: command not found. (Alsoecho...,slee...,true...,docke...,chrom...variants.)input: {"action":"writ...[truncated]","category":"CONFI...[truncated]","content":"YouTr...[truncated]"}→ outputError: Action 'writ...[truncated]' is not allowed.oldStringandnewStringtruncated to the same 5-char prefix → 43Error: No changes to apply: oldString and newString are identical(38edit, 5bash).input.filePathtruncated to/tmp/...[truncated]whilecontentwas full → a real 2203-byte file was created at the literal path/tmp/...[truncated].input: {"action":"write","category":"ARCHITECTURE","content":"<5 chars>...[truncated]"}(18-char content) → outputSaved memory [ID: 439] in ARCHITECTURE.— this created a corrupted long-term memory (content = 5 chars + sentinel). Several cross-session memories were created this way (5-char/sentinel content), i.e. the defect corrupts not just the session DB but the plugin's own durable memory store.Corroborating (not counted above): ~113 parts contain
...[truncated]in their output — that is opencode's own large-output truncation, unrelated to input corruption, and should not be conflated with this bug.Impact
ctx_memory writecalls with truncated content pass validation (validaction/category) and persist 5-char/sentinel memories, degrading future-session knowledge.docke...[truncated]), compounding context and cost.Suggested fix
The core issue is the representation of a dropped input. Options (any of which breaks the "model copies it" behavior):
[input dropped — original not shown; recover via ctx_expand(message=<N>)]rather than
docke...[truncated]. The currentctx_expand/ctx_searchrecovery mechanism already exists; the dropped input should point to it instead of inviting a verbatim copy.[N items]/[object]), reject or warn instead of executing (atool.execute.before-style guard), since such an input is always a transcription of a dropped value, never a genuine user intent.A minimal, safe change is option 1 (change the dropped-input rendering) — it removes the "looks like real data" property that triggers the copy behavior.
Reproduction (characteristic, not deterministic)
The trigger is model-dependent, so this is a characteristic reproduction rather than a unit test:
truncatedmode (the mode that appliestruncateToolPart).inputvalues asxxxxx...[truncated]andoutputas[dropped §N§].command not found,oldString and newString are identical, file at a truncated path).A deterministic assertion for a regression test: after a drop in
truncatedmode, no subsequently-persisted tool part's input may contain the...[truncated]sentinel /[N items]/[object]markers.Notes / scope
compactionin config controls the host's built-in compaction, which the plugin disables — it does not gate this behavior).[dropped §N§]outputs); the bug is specifically the downstream model re-issue it induces.Diagnostics
Plugin version
0.41.4
OpenCode version
1.18.29
Platform
linux x64
Client
OpenCode Web
Log output (optional)