Repository navigation
fix: strip leaked bare ACP refs from completed assistant text (#431) - #432
Conversation
📦 Built Plugin ArtifactBranch: Option A — Install from npm PR tag (recommended)opencode plugin opencode-acp@pr-432 --globalEach push to this PR publishes a new version under the Option B — Install from GitHubopencode plugin "github:ranxianglei/opencode-acp#2026-09-17_bare-ref-leak" --globalOption C — Download artifact
tar xzf opencode-acp-pr432.tgz
cp -r package/dist ~/.cache/opencode/packages/opencode-acp@latest/node_modules/opencode-acp/dist
This comment is automatically updated on each push. |
[bot] 🏷 PR review pass 1 — code verified against source, local run reproduced This is an agent-completed PR ( Branch state: Design audit (verified against source, not just the description):
Local verification (independent run): CI at time of check: build ✓, test (22) ✓, test (24) ✓, pr-validation ✓, build-artifact ✓; e2e still in progress (hence One meta note: reviewing ACP from inside an ACP-enabled session means dcp-tag fixtures in test files get stripped from my tool-output views in transit — I confirmed via numeric probes that the fixtures are intact on disk (e.g. Continuing: second independent review pass + e2e confirmation, then a final verdict. 中文摘要:已拉取分支并对照源码核对了单调分配不变量、同步 registry 查询和剥离顺序,本地 typecheck 与测试复现结果与 PR 描述一致(2 个失败均为沙箱只读 /tmp 的环境问题),CI 除 e2e 运行中外全绿;代码可以进入合并等待。 |
…uard (review nits)
…paction-epoch window
[bot] 🏷 Final verdict: ✅ ready for merge — dual-agent review complete, full CI green on head Follow-up since my first review pass: 1. Second independent review (separate agent, as required by the code/test review rules): verdict APPROVE, 4 LOW findings. Two were worth pinning with tests → fixed directly on the branch; two accepted as-is (no realistic failure path / cosmetic only). 2. Commits pushed to
3. CI on Findings accepted without change (for the record):
No blockers remain. Per project rules, merging is a human-only operation — please merge yourself when ready: #432 中文摘要:双 agent 独立复审通过(APPROVE,仅 4 个 LOW 级发现),已在 PR 分支直接补了 2 个测试钉住 b0 排除与 4 位 m 引用分支、修正 WORKLOG 测试计数;新 head |
Closes #431
Problem
With ACP enabled, assistant completions occasionally end with a bare ACP ref + garbage tail (e.g.
m00057 <random multilingual text>). The fragment carries no tag, so the existing tag-only sanitizer in theexperimental.text.completehook passes it through; the suffix is persisted into OpenCode's DB and re-enters later model context and compression summaries.Root cause layering (verified against v1.18.1 source):
messages.transform, so the completing message does not yet have its ref — the echoed value is exactly what becomes its own ref (matches the issue evidence "the leaked reference matches the affected assistant message").text.complete.Fix
Second sanitization pass in
experimental.text.complete, exploiting a hard invariant: refs/block IDs are allocated strictly monotonically (messageIds.nextRef,prune.messages.nextBlockIdare next-to-allocate). Any line-leading bare ref whose value is ≥ the next-to-allocate bound cannot be a legitimate citation of anything the model could have seen — it is by definition a future/hallucinated ID. Truncation runs from the first qualifying occurrence to end of string.Conservative guards (legitimate prose survives):
^|\n+ optional[ \t]) — embedded mentions like "see m00042" untouchedm+4–5 digits or lowercaseb+non-zero digits; uppercase product codes (M00057) untouchedNote: the issue's suggested "look up the completing message's exact ref via
input.messageID" is impossible at hook time — assignment happens on the next transform. The future-ref bound achieves the same containment with zero false positives by construction.Changes
lib/messages/utils.tsstripLeakedTrailingRefs(text, { nextMessageRef, nextBlockRef })lib/messages/index.tslib/hooks.tscreateTextCompleteHandler(registry, logger)— syncregistry.get(sessionID)lookup, applies new strip, warn-log withremovedChars+ 200-char tail previewindex.tstests/leaked-trailing-ref.test.tstests/hooks-permission.test.ts,tests/message-priority.test.tsdevlog/2026-09-17_bare-ref-leak/Verification
npm run typecheckclean;npm run buildclean/tmp):tests/soft-block.test.tsimport-timeEACCES mkdir '/tmp/opencode-dcp-dangerous-*', and onetoFilepath assertion intests/inactive-block-decompress.test.ts. Neither touches this change; expected green on CI.stripHallucinationsFromString(repro)returns input unchanged; new pipeline yields"Normal assistant progress message."for the [Bug]: Bare mNNNNN message refs leak into final assistant output and bypass sanitizer #431 repro shape.Known limitation (documented in DESIGN.md §8)
An echo of an already-assigned ref (value < bound) is deliberately not stripped — that value could be a legitimate citation, and all reported evidence points at the unassigned self-ref case. If that variant appears in the wild, a stricter second pass can be added.
Housekeeping note (pre-existing, not introduced here)
Repo-wide
npm run format:checkcurrently fails on master files due to prettier version drift (package.json pins^3.8.1; 3.9.x reformats differently). Kept this diff minimal instead of reformatting unrelated files; suggested as a separate housekeeping task.