Skip to content

ctx_expand recovery loop triggers Anthropic reasoning_extraction refusal on Opus 5.5 #700

Closed

Description

@procodingtools

Short description

Repeated ctx_expand calls to "recover / piece together" prior messages trips provider content-filter (reasoning_extraction) on claude-opus-5-5

What happened?

Expected: Agent can call ctx_expand a few times to rebuild context and continue working.

Observed: After ~6-7 consecutive ctx_expand calls in one session, the next
model turn fails hard with:

Provider blocked the response (reasoning_extraction): This request was blocked
as it seems to violate Anthropic's Terms of Service restrictions on reverse
engineering or duplicating model outputs.

Session ends with idle outcome: failed / provider.content-filter. Retry on the
same session re-fails. Fresh session + Sonnet works.

Repro pattern (from assistant reasoning + tool history):

  1. Long review session on Opus 5.5 (custom orchestrator agent, anthropic / claude-opus-5-5 / max variant)
  2. Agent enters a loop like:
    • reasoning: "I should keep recovering the context, so I'll expand messages 19 and 20…"
    • reasoning: "Since session history is empty, I need to check messages 17 onward to piece together where things left off"
    • reasoning: "I still need to recover messages 11-20…"
    • ctx_expand { message: 11 }, ctx_expand { start: 11, end: 14 }, ctx_expand { message: 6 }, …
  3. After ~7 of these, provider returns stop_reason: refusal / reasoning_extraction.

Shell / read / edit calls just before the block were benign (git status/diff, log grep, test file read) — the only suspicious shape is the ctx_expand + "recover / expand / full recovery / piece together prior outputs" wording, which matches Anthropic's documented trigger for this category.

Questions for maintainers:

  • Is there a recommended cap / backoff for consecutive ctx_expand calls to avoid looking like "duplicating model outputs"?
  • Should ctx_expand output avoid re-emitting prior reasoning blocks verbatim on Opus-class models?
  • Is per-model tool gating planned (e.g. deny ctx_expand only on Opus)? Today permissions are only global or per-agent.

Diagnostics

Plugin version: 0.49.6
OpenCode version: v2.0.31 (service 2.0.27)
Platform: linux x64 (AlmaLinux 8.10)
Client: OpenCode (auto-detected)
Harness: opencode

Plugin version

0.49.6

OpenCode version

2.0.31

Platform

linux x64

Client

OpenCode Web

Log output (optional)

[magic-context] registered slash commands: ctx-status ctx-recomp ctx-dream ctx-flush ctx-embed ctx-wrapup
[magic-context] @cortexkit/opencode-magic-context v2 TUI setup
[magic-context] clear_reasoning_age is deprecated and ignored. Magic Context now keeps reasoning up to a token budget, keep_reasoning_tokens (default 10,000).

[opencode server] Failed to drain Session — Session.StepFailedError: Provider blocked the response (reasoning_extraction):
This request was blocked as it seems to violate Anthropic's Terms of Service restrictions on reverse engineering
or duplicating model outputs. idle outcome: failed / provider.content-filter
Model: claude-opus-5-5 (anthropic, max variant)
Preceding turns: ~7x ctx_expand (message / start-end) + reasoning about "recovering context / piecing together earlier messages"

Activity

  1. magic-alfonso commented on Oct 16, 2026

    @magic-alfonso

    Thanks for the detailed report. Before deciding on any change, two facts from the source and a few questions.

    What ctx_expand returns: recovering a single message (ctx_expand(message=N)) and the default range view return the message's text and tool inputs/outputs only. Reasoning and thinking blocks are skipped entirely (packages/plugin/src/tools/ctx-expand/render.ts, renderPartFull). Only the opt-in verbose=true listing shows reasoning at all, as a 120-character preview per part. So the expanded output shouldn't re-emit earlier reasoning verbatim, though it does return the assistant's earlier text replies.

    The part that looks wrong to us is the loop itself: the model says "session history is empty" and keeps expanding messages 6 to 20. In a healthy session the older turns are summarized in a <session-history> block at the start of the conversation, and the newest messages are already visible, so there should be nothing to piece together. We'd like to find out why that didn't hold here, especially since OpenCode 2.0.31 is newer than the versions we've tested.

    Could you share:

    1. The output of npx @cortexkit/magic-context@latest doctor --issue from that machine.
    2. What one or two of those ctx_expand calls returned. Did any say a message was missing, or that the range is the live tail and already visible?
    3. Roughly how many messages the session had when this started, and whether it had run /ctx-wrapup or /ctx-recomp, or been compacted, shortly before.
    4. Whether the orchestrator agent's own prompt tells it to recover or reconstruct context.

    We're checking the same path on OpenCode 2.0.31 in the meantime.

  2. procodingtools commented on Oct 16, 2026

    @procodingtools
    Author

    Thanks for the detailed report. Before deciding on any change, two facts from the source and a few questions.

    What ctx_expand returns: recovering a single message (ctx_expand(message=N)) and the default range view return the message's text and tool inputs/outputs only. Reasoning and thinking blocks are skipped entirely (packages/plugin/src/tools/ctx-expand/render.ts, renderPartFull). Only the opt-in verbose=true listing shows reasoning at all, as a 120-character preview per part. So the expanded output shouldn't re-emit earlier reasoning verbatim, though it does return the assistant's earlier text replies.

    The part that looks wrong to us is the loop itself: the model says "session history is empty" and keeps expanding messages 6 to 20. In a healthy session the older turns are summarized in a <session-history> block at the start of the conversation, and the newest messages are already visible, so there should be nothing to piece together. We'd like to find out why that didn't hold here, especially since OpenCode 2.0.31 is newer than the versions we've tested.

    Could you share:

    1. The output of npx @cortexkit/magic-context@latest doctor --issue from that machine.
    2. What one or two of those ctx_expand calls returned. Did any say a message was missing, or that the range is the live tail and already visible?
    3. Roughly how many messages the session had when this started, and whether it had run /ctx-wrapup or /ctx-recomp, or been compacted, shortly before.
    4. Whether the orchestrator agent's own prompt tells it to recover or reconstruct context.

    We're checking the same path on OpenCode 2.0.31 in the meantime.

    Thanks — helpful to know reasoning is skipped in renderPartFull.

    1. doctor: plugin 0.49.6, OpenCode v2.0.31, linux x64, OpenCode auto-detected.
      Raw doctor --issue lists machine-local project identities so I don't
      want to paste it verbatim — tell me which section helps and I'll redact.

    2. The expands succeeded, none said missing/already-visible. Example:
      ctx_expand { message: 11 } → [11] A (assistant) — full recovery:

      • prior tool input/output. I can share redacted headers privately,
        raw bodies contain project file lists.
    3. 56 stored messages (seq 5–582), ~90min session, no compaction timestamp,
      no /ctx-wrapup or /ctx-recomp immediately before. Loop was the last
      thing before the refusal.

    4. Orchestrator prompt does not mention recover/reconstruct/ctx_expand
      (213 lines, grepped). The "keep recovering context / piece together"
      phrasing is model-generated.

    So the refusal seems to be on the request shape (repeated expand +
    "recover prior outputs" intent on Opus 5.5 max), not on reasoning
    leaking through ctx_expand output. Happy to test a cap/backoff or a
    "stop after N expands" guard if you ship one.

    Image
  3. magic-alfonso commented on Oct 16, 2026

    @magic-alfonso

    Correcting my earlier reply: the question I asked can't be answered from the session view, because placeholders only exist in what the model is sent; the conversation you see always shows the original messages. Please ignore it.

    Thanks, that narrows it down a lot. You don't need to paste the doctor output. We're also checking what doctor --issue includes about local projects, so reports can be shared safely.

    A 56-message session with no compaction is young enough that it may not have a <session-history> summary yet. Our current theory: some of the early messages had been dropped from what the model sees, to save space, and the model tried to rebuild what was behind them, which is what the ctx_expand guidance says the tool is for.

    What would confirm it, with no message content: the output of /ctx-status in that session (or a new message in it, if it's still open). It shows the number of history summaries and drops. We're reproducing the same session shape on OpenCode 2.0.31 and will follow up here.

  4. magic-alfonso commented on Oct 16, 2026

    @magic-alfonso

    Correcting my earlier reply: the fix changes how this cleanup measures age, not when it is allowed to run. Details below.

    Thanks, that rules out the compaction theory, and it helped us find a likely cause on our side.

    Magic Context has an automatic cleanup that drops older tool outputs whenever the prompt cache is rebuilt anyway. It counts an output as old once it has survived one rebuild. The problem is that a rebuild also happens when the cache simply expires after a few idle minutes, or when you switch models. So two such rebuilds can remove most earlier tool outputs from a young session, even though the context is nowhere near full. The model then sees an empty history block (no summary exists yet in a young session) plus placeholders where its earlier work was, and tries to get it back with ctx_expand. We reproduced the retrieval side on a real OpenCode 2.0.31 host: seven expands in a row, including the same message four times, all succeed. ctx_expand doesn't return earlier reasoning blocks; verbose=true shows at most a 120-character preview of each.

    A fix is in progress so that an output only counts as old once the conversation has actually moved on past it, not because idle time or a model switch triggered extra rebuilds. Three quick answers would confirm this is what hit you:

    1. What context window size were you using?
    2. Did you switch models during that session?
    3. Were there gaps of 5 minutes or more between your messages?

    While reproducing this we also found a separate bug: asking ctx_expand for a message number past the end of a session returns the first message under that number. A fix for that is ready and will ship in the next release.

  5. magic-alfonso commented on Oct 17, 2026

    @magic-alfonso

    Released in v0.49.9.

    The root cause on our side was age-based cleanup of tool outputs. It counted cache rebuilds, so in a young session two idle gaps of five minutes, or a model switch, could clear outputs the current task still needed, and the model then tried to recover them with ctx_expand. Now a tool output only counts as old after three newer user turns, or once newer content fills 25% of the context window. Background notices and injected history don't count as turns. This applies on OpenCode 1 and 2, Pi, Oh My Pi and Rust mode, and cleanup still only happens on a request that already rebuilds the cache.

    0.49.8 also fixed ctx_expand on OpenCode 2 returning the session's first message for an ordinal past the end. If you still see a recovery loop on 0.49.9, please reopen with the output of /ctx-status from that session.

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

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions