Skip to content

0.49.23 (omp 18.12.6): every turn refused on resumed sessions: prepared turn structure ... expectedRole=missing actualRole=system comparison=count (trailing omp tool-control system message isn't modeled) #785

Closed

Description

@tsankotsanev

Versions

  • @cortexkit/pi-magic-context 0.49.23
  • omp 18.12.6 (upgraded from 18.12.3 with MC 0.49.13, which had worked)
  • Model: anthropic/claude-opus-5-5 (anthropic-messages; supportsMidConversationSystem)

Symptom
After the upgrade, every turn on sessions created before the upgrade is refused. Brand-new sessions work.

Magic Context could not safely prepare this turn; send your message again. stage=provider dispatch ... refused (provider request does not match this pass's managed dispatch (prepared turn structure); row=11 expectedRows=11 actualRows=12 expectedRole=missing actualRole=system expectedCalls=0 actualCalls=0 expectedResults=0 actualResults=0 comparison=count)

Retrying doesn't help: expectedRows grows by 2 per attempt and the provider payload always has exactly one extra row at the end.

Cause
We logged the last 3 provider messages at the mismatch, just before the throw in the dispatch check. The extra row is omp's request-control message, a trailing system message holding only tool_addition blocks:

{"role":"system","content":[
  {"type":"tool_addition","tool":{"type":"tool_reference","name":"_ctx_search"}},
  {"type":"tool_addition","tool":{"type":"tool_reference","name":"_ctx_memory"}},
  {"type":"tool_addition","tool":{"type":"tool_reference","name":"_ctx_note"}},
  {"type":"tool_addition","tool":{"type":"tool_reference","name":"_ctx_expand"}},
  {"type":"tool_addition","tool":{"type":"tool_reference","name":"_ctx_reduce"}}
]}

omp's Anthropic provider (pi-ai/src/providers/anthropic.ts) inserts these tool-change and effort controls via insertAnthropicControlMarkers while it builds the request. That happens after MC's context pass, so the prepared plan never contains the row. payloadStructure only skips system/developer messages while rows.length === 0, so the trailing control is counted as a conversation row and the count comparison fails.

The control appears when the session's recorded tool set differs from the current one, which is why only resumed pre-upgrade sessions hit it. The control is only recorded after a successful request, and the refusal prevents that, so the session can never recover by itself.

Local fix (verified)
In payloadStructure, right after the leading system/developer skip:

if (message.role === "system" && rows.length > 0 && Array.isArray(message.content)
    && message.content.every((block) => ["tool_addition", "tool_removal"].includes(object8(block)?.type)))
  continue;

With this, the previously refused session replies normally and new sessions are unaffected. Empty effort-only controls (content: []) are also skipped, because every on an empty array is true.

Suggested upstream fix: treat omp request controls (tool_addition / tool_removal / effort-only system params) as non-conversation rows in the structure comparison. Alternatively, model them in the prepared plan the same way omp's planAnthropicEffortControls / tool plan does.

Repro (observed path; not reproduced from scratch)

  1. On omp 18.12.3 + MC 0.49.13, create a session with Opus 5.5 and send a few turns.
  2. Upgrade to omp 18.12.6 + MC 0.49.23.
  3. Resume the session and send any message: refused on every attempt.

Activity

  1. magic-alfonso commented on Oct 26, 2026

    @magic-alfonso

    Thanks for the precise diagnosis and the logged payload. That made this quick to confirm.

    It matches our code: the Pi dispatch check only skips system/developer rows that come before the first conversation row, so Oh My Pi's trailing request-control message (the tool_addition blocks) is counted as an extra conversation row and the count comparison refuses the turn. And because Oh My Pi only records the control after a successful request, the session can't get past it on its own.

    The likely reason only older sessions hit it: since 0.49.20, Magic Context's tools are always loaded on Oh My Pi rather than discovered on demand, so a session started before that sees its tool set change on the first turn after upgrading, and Oh My Pi adds the control for the five ctx_* tools.

    Until a fix ships, new sessions aren't affected, so starting a new session is the workaround for anything you need to keep working on. We'll follow up here with the fix.

  2. added
    design-approvedDesign agreed by a maintainer; a PR referencing this issue can be reviewed
    on Oct 26, 2026
  3. magic-alfonso commented on Oct 27, 2026

    @magic-alfonso

    Fixed in 0.49.24, with your PR #786. Thanks @tsankotsanev for both the report and the fix.

    The Oh My Pi request check now skips the tool-control messages Oh My Pi adds when a session's tool list changes, wherever they appear in the request. Resumed sessions no longer have every turn refused. A message that carries real text in that position is still refused, as before.

    Update the Pi package and restart Oh My Pi to pick it up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    design-approvedDesign agreed by a maintainer; a PR referencing this issue can be reviewed

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions