Skip to content

fix(provider): prevent spurious tool events from Qwen 3.7 Plus/Max (via OpenRouter)#33504

Closed
BernhardGruen wants to merge 1 commit into
anomalyco:devfrom
BernhardGruen:qwen-spurious-events-fix
Closed

fix(provider): prevent spurious tool events from Qwen 3.7 Plus/Max (via OpenRouter)#33504
BernhardGruen wants to merge 1 commit into
anomalyco:devfrom
BernhardGruen:qwen-spurious-events-fix

Conversation

@BernhardGruen

@BernhardGruen BernhardGruen commented Jun 23, 2026

Copy link
Copy Markdown

Issue for this PR

Closes #21090

Related

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

This PR fixes "unknown" / "invalid" tool call messages that occur repeatedly.

Problem

Qwen 3.7 Plus/Max (via OpenRouter) sends spurious tool-input-delta and tool-input-end events that arrive AFTER tool-result/tool-error for the same call ID, violating the expected event sequence:

Expected: tool-input-start → delta → end → call → result → [done]
Actual:   tool-input-start → delta → end → call → result → delta → end → [done]

This causes:

  • Empty tool names ("") displayed in UI
  • SchemaError: Missing key at ["name"] errors
  • Tool execution aborted (up to 15 per session)
  • ✗ unknown Unknown failed errors

Root Cause

OpenRouter's proxy layer reorders events for streaming optimization. The processor in processor.ts accepted these spurious events and created phantom tool entries with no name and corrupted state.

Solution

Added guards in processor.ts to reject spurious events for already-completed tool calls:

// In tool-input-delta handler:
if (!(value.id in ctx.toolcalls)) return

// In tool-input-end handler:
if (!(value.id in ctx.toolcalls)) return

This is the minimal, sufficient fix per YAGNI principle.

How did you verify your code works?

I did multiple A/B tests (3 per run to increase significance) that compared OpenCode 1.17.9 using Qwen 3.7 Plus (via OpenRouter) with my development branch that contained only the fix.

A/B-Test Evidence

Tested with Qwen 3.7 Plus via OpenRouter, 3 runs per configuration:

Configuration ✗-unknown (max) aborted (max) invalid (max) Result
Original (dev) 14 15 5 ❌ Catastrophic
PR #32909 alone 6 6 0 ❌ Critical
Guards only 0 0 0 Clean
Guards + ai-sdk.ts 0 0 0 ✅ Clean

Finding: processor.ts guards are necessary and sufficient. Additional ai-sdk.ts changes provide no measurable benefit.

Testing

  • ✅ 3 new integration tests in processor-effect.test.ts
  • ✅ All 18 processor tests pass
  • ✅ A/B tests prove fix eliminates all symptoms

Screenshots / recordings

If this is a UI change, please include a screenshot or recording.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

If you do not follow this template your PR will be automatically rejected.

@github-actions github-actions Bot added the needs:compliance This means the issue will auto-close after 2 hours. label Jun 23, 2026
@github-actions

Copy link
Copy Markdown
Contributor

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

Related PR Found

PR #32909: fix(session): keep AI SDK tool result names
#32909

This PR is directly related to the current PR #33504. According to the PR description, #32909 was an insufficient upstream fix on its own—the A/B testing shows that #32909 alone only reduced errors from 14-15 down to 6, but this PR's guards are necessary and sufficient to eliminate all symptoms (0 errors). The PR documentation explicitly notes this relationship and how the two fixes work together.

@github-actions github-actions Bot removed the needs:compliance This means the issue will auto-close after 2 hours. label Jun 23, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thanks for updating your PR! It now meets our contributing guidelines. 👍

…uter

Qwen-Plus via OpenRouter sends spurious tool-input-delta and tool-input-end
events AFTER tool-result for the same call ID, causing:
- Empty tool names ("unknown") in UI
- SchemaError: Missing key at ["name"]
- Tool execution aborted (up to 15 per session)

Fix: Add guards in processor.ts to reject events for tool calls that are
not in ctx.toolcalls (i.e., already completed or unknown).

Added 3 integration tests in processor-effect.test.ts.

A/B tested with 3 runs per configuration:
- Original: 14/15/5 errors (✗-unknown/aborted/invalid)
- Guards only: 0/0/0 errors
@BernhardGruen BernhardGruen force-pushed the qwen-spurious-events-fix branch from c07db8a to c522da8 Compare June 24, 2026 07:25
@BernhardGruen

Copy link
Copy Markdown
Author

Will create a new one.

@BernhardGruen

Copy link
Copy Markdown
Author

New PR: #33622

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.

Opencode - Always "error=Model tried to call unavailable tool"

1 participant