Repository navigation
bug(omp): tag stripping leaves unsigned text with stale Responses payload and blocks subsequent turns #766
Description
What happened
With @cortexkit/pi-magic-context 0.49.16 and OMP 18.12.6, an existing Responses session repeatedly refuses new turns before provider dispatch:
provider request does not match this pass's managed dispatch (prepared turn structure);
row=28 expectedRows=73 actualRows=73 expectedRole=assistant actualRole=assistant
expectedCalls=0 actualCalls=0 expectedResults=0 actualResults=0 comparison=content
Resending and restarting do not repair the persisted history. This is an OMP tag-rewrite/native-replay interaction, distinct from a row-count or leading-system-row mismatch.
Reproduction and cause
A minimal affected assistant message has these two representations (provider/model names are illustrative):
const message = {
role: "assistant", api: "openai-responses", provider: "example", model: "gpt-5",
stopReason: "stop", timestamp: 1,
content: [{ type: "text", text: "Hello" }], // textSignature absent after rewrite
providerPayload: {
type: "openaiResponsesHistory", provider: "example", dt: true,
items: [{
type: "message", role: "assistant", id: "msg_example",
phase: "final_answer", status: "completed",
content: [{ type: "output_text", text: "§12§ Hello", annotations: [] }]
}]
}
};registerOmpAssistantMessageStripreturns stripped replacement content through OMP'sassistant_messagehook.- In OMP 18.12.6,
ExtensionRunner.emitAssistantMessageremovestextSignaturefrom rewritten text. The SDK updatesmessage.content, but retains the originalproviderPayload. - On a later turn,
piResponsesAssistantTexts(message, model)cannot bind the unsigned block to the native message and expects["Hello", null]. - OMP's actual
buildResponsesInput, with native history replay enabled, emits["§12§ Hello", "final_answer"]from the retained payload. The dispatch fence correctly rejects this mismatch.
Verified the planner half against master 79ce01a522ae5a9d65d6bb2ac81aadf16fa8285a, and the wire half against installed OMP 18.12.6. No live model request is required to reproduce these two different text/phase pairs.
Relevant upstream locations:
Proposed scope for maintainer approval
A local mitigation reconciles only OMP messages before the managed context transform. It uses copy-on-write messages without the stale native payload only if all of the following hold:
- Responses-family API, same model/provider, assistant message;
- incremental
openaiResponsesHistorypayload (dt: true); - at least one text block has lost its signature;
- native output text blocks and visible text blocks correspond one-to-one;
- every pair is equal after removing only a leading
§N§tag, and at least one pair differs before that removal.
All other mismatches remain subject to the existing refusal. The normalization is gated by resolvePiHarnessKind() === "omp"; it does not change OpenCode adapters, Pi behavior, shared configuration, or database schemas. It repairs request preparation for existing affected history without rewriting session files.
Tradeoff: dropping the stale native payload can change native reasoning replay/cache reuse for those affected messages. This is a narrow local recovery measure, not a request to remove the dispatch fence. A maintainer may prefer an OMP-side invalidation fix or a binding-preserving synchronization of the native payload. Whichever approach is chosen should cover already persisted affected messages as well as new replies.
Local validation
- Seven affected real messages: actual planner/encoder structure checks fail before mitigation and pass afterward, including messages containing tool calls.
- Whole original history conversion: 784 normalized rows, content mismatch before mitigation, complete structural match after mitigation. This is an offline raw-history check, not a replay of the original 73-row managed compression pass.
- 14 regression checks / 53 assertions pass: affected history, no mutation of originals, idempotence, unrelated edits, existing signatures, other providers, malformed payloads, non-incremental history, and the installed OMP-only guard.
- Two additional sanitized-fixture tests against the current upstream planner pass.
- Guard tests confirm the mitigation is not invoked for non-OMP harness values and preserves the exact event object there. These are isolation tests, not full OpenCode/Pi/Rust end-to-end suites.
- Local bundle syntax validation passes. No online post-restart round trip or full upstream test suite has been claimed.
Harness coverage / impact
| Harness | Proposed change / evidence |
|---|---|
| OMP | Target surface; positive regression coverage using OMP encoder and actual adapter/planner logic. |
| Pi | Shares the package, but OMP guard must remain false; negative guard coverage. |
| OpenCode 1 | Separate adapter; this OMP hook/replay-normalization surface is absent; no source/config/schema change proposed. |
| OpenCode 2 | Separate adapter; this OMP hook/replay-normalization surface is absent; no source/config/schema change proposed. |
| Rust module | OMP host-specific hook is absent; no Rust or shared schema/config change proposed. |
Following CONTRIBUTING.md, this issue requests agreement on the fix before a PR. Please confirm the preferred recovery approach and apply design-approved when appropriate. A PR would include sanitized regressions rather than private session data.
Environment / diagnostics
- Magic Context: 0.49.16
- Client: OMP 18.12.6
- Platform: macOS
- Bun: 1.4.2
- API: openai-responses
Activity
sisttri commentedon Oct 23, 2026
I hit this immediately after running
/fork, then/renamein OMP. The next prompt was refused withcomparison=content; retrying and restarting the forked session did not help.Comparing the parent and forked transcripts showed that all seven affected messages already had the same text, signature state, and native payload in the parent. Forking copied the existing inconsistency. Adding or removing the rename record made no difference to the offline request payload.
So
/forkmay expose the existing replay mismatch when the new session prepares its first request, but I have not isolated that transition yet./renamecurrently appears unrelated. Please include the first request after a fork in regression coverage, and compare fork-only with fork-then-rename.magic-alfonso commentedon Oct 23, 2026
Thanks for the precise report and the reproduction, it makes this easy to check. The planner and dispatch check you point at are ours, and we're verifying each step against Oh My Pi's own source now, including the first request after a fork. We'll reply here with what we find and which fix we pick. Please hold off on a pull request until then.
magic-alfonso commentedon Oct 23, 2026
Confirmed. Your trace is exact on Oh My Pi 18.12.3 and 18.12.6. A saved session copies the mismatch through a restart and through
/fork, with or without/rename.We're fixing it on our side by making the request check follow exactly what Oh My Pi replays for these messages. Nothing is rewritten in the request, the session files or the native payload, so native reasoning and phase replay stay intact, and affected sessions recover without any edit. We considered dropping the stale payload, as you proposed, but that loses the text's phase and can lose native-only reasoning items. Signature, structure and every other kind of mismatch still refuse as they do today.
The fix ships in the next patch, crediting you. Thanks again for the precise report and for holding the PR.
sisttri commentedon Oct 23, 2026
I hit this again with a newly generated assistant reply containing two tool calls. The persisted text was
Hello, while its native payload was equivalent to§12§ Hello\nwithphase: commentary.This adds an important case to the original report:
stripPersistedAssistantText()also calls.trim()and strips tag notation beyond the leading prefix. My initial prefix-only workaround missed the trailing newline. This supersedes the leading-prefix-only matching criterion in the issue's local mitigation proposal. I agree that preserving native replay, phase and reasoning is preferable for the upstream fix you described.Exact path to inspect
stripPersistedAssistantText: tag cleanup followed by.trim().strippedAssistantContent/ OMP hook: returns rewritten text.packages/coding-agent/src/extensibility/extensions/runner.ts,emitAssistantMessage: removes the rewritten block'stextSignature;packages/coding-agent/src/sdk.ts,transformAssistantMessage: assigns replacementcontentwhile retainingproviderPayload.piResponsesAssistantTextsplans typed text/null phase; OMPpackages/ai/src/providers/openai-shared.ts,buildResponsesInput, replays native text/commentary phase.Portable reproduction — no private environment required
I tested this in a separate directory with a freshly installed
@oh-my-pi/pi-ai@18.12.6under Bun 1.4.2. All text, model/provider names, IDs, tools and arguments below are synthetic. No API key, model request, session transcript, database, global configuration or local hotfix is required.The script starts from the post-OMP-rewrite message shape, JSON-round-trips it, and exercises the real cleanup function, planner and OMP encoder. It isolates the mismatch; it does not instantiate the host's extension runner or the full context pipeline.
Expected output on the pinned baseline: all three cases report planned
["Hello",null]versus actual[rawText,"commentary"], with both tool calls preserved, followed byReproduced all 3 text/phase mismatches without a model request.These assertions characterize the bug; they intentionally expect inequality and should change when converted to the fixed OMP-path regression.Acceptance coverage for the upstream fix
commentaryphase.My expanded local mitigation passes 19 tests / 63 assertions. Latest full-history offline replay also changes from a content mismatch to a match, but its row count differs from the live pass because the complete live configuration was not replayed. I have not claimed an online round-trip after that update. This is separate from the tag-snapshot initialization issue #769.
magic-alfonso commentedon Oct 25, 2026
Shipped in 0.49.21 (
@cortexkit/pi-magic-context). This is on Oh My Pi's OpenAI Responses route, when Oh My Pi drops a reply's text signature after Magic Context strips its tag. The request check now accepts Oh My Pi's replayed saved text if Magic Context's own tag cleanup, applied to that text, gives exactly what Magic Context prepared. That covers a trailing newline and tags elsewhere in the text, as well as the leading tag.Anything else still refuses: a signature that's present, a different model, provider or API, a different payload type, or a different number of text pieces. Restart Oh My Pi after updating; there's no migration. Thanks, @sisttri.