Skip to content

Conversation

@coleleavitt
Copy link
Contributor

@coleleavitt coleleavitt commented Jan 16, 2026

Fixes #6418

Problem

When switching from models like GLM 4.7 or MiniMax to Claude with extended thinking enabled, users get API errors:

messages.X.content.0: Invalid 'signature' in 'thinking' block

or

messages.X.content.0.type: Expected 'thinking' or 'redacted_thinking', but found 'text'

This happens because:

  1. Other models produce thinking blocks with signatures that are incompatible with Claude's signature validation
  2. Claude requires the last assistant message to start with a valid thinking block when thinking is enabled
  3. reasoning parts from other models don't have the signature format Claude expects

Solution

In normalizeMessages(), when the target model is Claude:

  1. Convert thinking blocks with signatures to wrapped text - Preserves the reasoning content while removing the invalid signature. Wrapped in <assistant_thinking> tags.
  2. Convert reasoning parts to text - Same approach, wrapped in <assistant_reasoning> tags.
  3. Track which messages had thinking blocks converted - Using a Set to avoid incorrectly removing messages that we already handled.
  4. Only remove last assistant message if safe - Only when it doesn't start with valid thinking, has no tool calls, AND didn't have converted thinking blocks.

This approach preserves tool_use/tool_result pairing while still handling the signature validation issues.

Changes

  • packages/opencode/src/provider/transform.ts: Added thinking block conversion logic in Claude-specific section
  • packages/opencode/test/provider/thinking-blocks.test.ts: New test file with comprehensive coverage
  • packages/opencode/test/provider/transform.test.ts: Updated existing test expectation

Testing

  1. Start a session with GLM 4.7 or MiniMax (models that generate thinking/reasoning)
  2. Send a few messages, including one that triggers tool use
  3. Switch to Claude with extended thinking enabled
  4. Send a message - should no longer get signature errors
  5. Tool results should still work correctly

Related

@github-actions
Copy link
Contributor

The following comment was made by an LLM, it may be inaccurate:

Potential Duplicate Found

PR #6748: fix: strip incompatible thinking blocks when switching to Anthropic models
#6748

Why it's related:

The current PR (#8958) explicitly states it "Supersedes #6748 (similar approach but that PR has been stale)", so you should check if #6748 needs to be closed or if this PR is intended to replace it.

@coleleavitt coleleavitt force-pushed the fix/strip-thinking-blocks-model-switch branch from 0980576 to b498721 Compare January 16, 2026 20:29
- Convert thinking blocks with signatures to wrapped text (preserves content)
- Convert reasoning blocks to wrapped text for Claude
- Track converted thinking messages to avoid incorrect removal
- Only remove last assistant message if safe (no tool calls, no converted thinking)
- Preserve tool_use/tool_result pairing

Fixes the 'Invalid signature in thinking block' and 'Expected thinking or
redacted_thinking but found text' errors when switching from GLM/MiniMax
to Claude with extended thinking mid-session.
@coleleavitt coleleavitt force-pushed the fix/strip-thinking-blocks-model-switch branch from 0a3c6c9 to 0d7f9e8 Compare January 16, 2026 21:14
@coleleavitt
Copy link
Contributor Author

Summary

Fixes the Invalid 'signature' in 'thinking' block error when switching from models like GLM 4.7 or MiniMax to Claude with extended thinking enabled.

The Fix

  1. Convert thinking blocks with signatures to wrapped text - Preserves reasoning content in <assistant_thinking> tags while removing incompatible signatures
  2. Convert reasoning blocks to wrapped text - Same approach with <assistant_reasoning> tags
  3. Track converted messages - Prevents incorrectly removing messages we already handled
  4. Safe last-message removal - Only removes last assistant if it doesn't start with valid thinking, has no tool calls, AND didn't have converted thinking

This preserves tool_use/tool_result pairing while handling signature validation.

Tested

  • MiniMax → Claude with extended thinking: ✅ No signature errors
  • Tool use preserved across model switches: ✅

Copy link

@d-init-d d-init-d left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Detailed Code Review

Thanks for tackling this issue @coleleavitt! The approach is solid - converting incompatible thinking blocks to wrapped text is the right direction. However, I've identified some issues that should be addressed before merging.

🐛 Critical Bugs

1. Devstral support regression (line ~133 in patch)
The original code includes devstral in the Mistral check, but this PR removes it:

// Original:
if (model.providerID === "mistral" || model.api.id.toLowerCase().includes("mistral") || model.api.id.toLocaleLowerCase().includes("devstral"))

// PR:
if (model.providerID === "mistral" || model.api.id.toLowerCase().includes("mistral"))

This will break devstral model support!

2. Inconsistent tag naming
PR description mentions <assistant_reasoning> for reasoning parts, but code uses <assistant_thinking> for both. This inconsistency makes it harder to distinguish between converted thinking vs reasoning blocks when debugging.

⚠️ Edge Cases Not Handled

3. Empty content array after filtering
After converting and filtering parts with .filter((part: any) => part !== null), msg.content could become []. Claude API rejects empty content arrays with validation errors. Should add a check to filter out messages with empty content:

if (msg.content.length === 0) return undefined
// Then filter undefined messages at the end

4. Valid Claude thinking blocks could be incorrectly converted
Currently, ANY thinking block with a signature gets converted. What about valid Claude thinking blocks that have proper Anthropic signatures from a previous Claude turn in the same session?

Consider checking signature format:

const isValidClaudeSignature = part.signature?.startsWith('ErUB')
if (part.type === "thinking" && part.signature && !isValidClaudeSignature) {
  // Convert only invalid signatures
}

🔧 Suggested Improvements

5. Type safety
Multiple uses of any casts ((part: any), (options as any)) could be replaced with proper types for better maintainability.

6. Extract helper function
The Claude-specific block has grown significantly (from ~10 lines to ~50+ lines). Consider extracting to a dedicated function like normalizeClaudeMessages() for better readability.

7. Missing test cases in thinking-blocks.test.ts
Good test coverage overall, but consider adding:

  • Empty thinking text (edge case)
  • Multiple thinking blocks in single message
  • Valid Claude thinking blocks (should NOT be converted)
  • Session with mixed Claude + non-Claude turns

I'm working on an improved PR that addresses these issues. Happy to collaborate if you'd like to incorporate these fixes into your PR instead!

Files reviewed:

  • packages/opencode/src/provider/transform.ts - Core logic changes
  • packages/opencode/test/provider/thinking-blocks.test.ts - New test file
  • packages/opencode/test/provider/transform.test.ts - Test expectation update

d-init-d added a commit to d-init-d/opencode that referenced this pull request Feb 3, 2026
Fixes anomalyco#6418

## Problem
When switching from models like GLM 4.7 or MiniMax to Claude with
extended thinking enabled, users get API errors because other models
produce thinking blocks with signatures incompatible with Claude.

## Solution
1. Add `isValidClaudeSignature()` helper to detect valid Claude signatures
2. Extract `normalizeClaudeThinkingBlocks()` for better code organization
3. Convert ONLY invalid signatures - preserve valid Claude thinking blocks
4. Use distinct `<assistant_reasoning>` tags for reasoning blocks
5. Filter out messages with empty content after processing
6. Preserve devstral support in Mistral check

## Improvements over PR anomalyco#8958
- Only converts thinking blocks with INVALID signatures (not all)
- Uses distinct tags for thinking vs reasoning
- Handles empty content edge case
- Better type safety (reduced `any` usage)
- Extracted helper function for maintainability
- More comprehensive test coverage

## Testing
1. Start session with GLM 4.7 or MiniMax
2. Send messages including tool use
3. Switch to Claude with extended thinking
4. Should no longer get signature errors
5. Tool results still work correctly"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Error: Invalid signature in thinking block

2 participants