Skip to content

Anthropic redacted_thinking blocks are stripped in stripClearedReasoning() #8

@tomolom

Description

@tomolom

Summary

When Magic Context transforms messages for Anthropic sessions, it can remove redacted_thinking parts from assistant messages. Anthropic requires those blocks to be replayed unchanged, so the request is rejected with errors like:

messages.1.content.11: thinking or redacted_thinking blocks in the latest assistant message cannot be modified

Root cause

In packages/plugin/src/hooks/magic-context/strip-content.ts, stripClearedReasoning() includes redacted_thinking in the set of part types it may strip:

const CLEARED_REASONING_TYPES = new Set([thinking, reasoning, redacted_thinking]);

But the strip predicate only preserves parts that have a non-cleared thinking or text field:

const thinking = thinking in part ? (part.thinking as string | undefined) : undefined;
const text = text in part ? (part.text as string | undefined) : undefined;
return (
  (thinking !== undefined && thinking !== [cleared]) ||
  (text !== undefined && text !== [cleared])
);

That works for thinking / reasoning, but not for Anthropic redacted_thinking blocks, which do not use those fields. As a result, valid redacted_thinking parts fail the predicate and are removed.

Why this looks like the bug

There is already a hint elsewhere in the code that redacted_thinking should not be treated as a normal mutable reasoning part:

packages/plugin/src/hooks/magic-context/tag-content-primitives.ts

export function isThinkingPart(part: unknown): part is ThinkingLikePart {
  if (part === null || typeof part !== object) return false;
  const candidate = part as Record<string, unknown>;
  return candidate.type === thinking || candidate.type === reasoning;
}

isThinkingPart() excludes redacted_thinking, but stripClearedReasoning() still targets it. That mismatch is what seems to break Anthropic replay.

Minimal fix

Remove redacted_thinking from CLEARED_REASONING_TYPES:

- const CLEARED_REASONING_TYPES = new Set([thinking, reasoning, redacted_thinking]);
+ const CLEARED_REASONING_TYPES = new Set([thinking, reasoning]);

Safer fix

Even with the one-line fix above, it would be good to make the preservation explicit:

if (partType === redacted_thinking) return true;

inside stripClearedReasoning() before the mutable reasoning checks.

Extra hardening suggestion

There are other transform paths that clear adjacent assistant reasoning when tagged text or tool output is rewritten. For Anthropic sessions, it may be worth adding a provider-aware rule to never mutate the latest assistant message if it contains thinking or redacted_thinking parts.

Environment where this surfaced

  • @cortexkit/opencode-magic-context@0.8.3
  • Anthropic models in OpenCode
  • Error seen during transformed follow-up requests after assistant reasoning was present

If useful, I can also open a PR with the minimal patch.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions