Skip to content

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

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

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

Conversation

@BernhardGruen

Copy link
Copy Markdown

Issue for this PR

Closes #33618

Related

  • Possibly related PR #32909: Insufficient without processor.ts guards
  • Affects OpenRouter-routed LLMs (Qwen 3.7 Plus/Max confirmed)

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

Before fix: https://opncd.ai/share/XIq1kE7k

After fix: https://opncd.ai/share/X0zavMRl

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.

…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
@github-actions

Copy link
Copy Markdown
Contributor

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

Finding:

The PR #33622 references PR #32909 ("fix(session): keep AI SDK tool result names") as possibly related. According to the PR description, #32909 alone was insufficient to fix the issue (reducing errors from 14 to 6), but the guards added in this PR (#33622) are necessary and sufficient (reducing errors to 0).

However, PR #32909 is a related PR that was already merged/discussed as a partial solution, not a duplicate. This PR (#33622) is the complete fix.

No duplicate PRs found

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.

Qwen 3.7 Plus/Max (via OpenRouter) unknown/invalid tool calls

1 participant