Repository navigation
Claude V2 adapter passes bare "claude" to the Agent SDK on Windows, so every turn fails with "Provider turn failed." #15011
Description
What happened
After updating the desktop app to a nightly build, every Claude turn fails with "Provider turn failed." Codex turns on the same install keep working.
Diagnosis
On Windows, ClaudeAdapterV2 passes the configured binaryPath to the Claude Agent SDK as pathToClaudeCodeExecutable unchanged:
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts:871(tagv0.0.46-nightly.20261003.2610;:875onmain):pathToClaudeCodeExecutable: input.settings.binaryPathClaudeAdapterV2.ts:7664setsbinaryPathtoexpandHomePath(config.binaryPath), which is"claude"with the default settings.
The SDK spawns that value without a shell and without PATH/PATHEXT resolution, so a bare claude cannot be found on Windows. resolveClaudeSdkExecutablePath in apps/server/src/provider/Drivers/ClaudeExecutable.ts already handles this case (added in #3740 for v1). It resolves claude through PATH and follows the npm claude.cmd shim to node_modules/@anthropic-ai/claude-code/bin/claude.exe. That helper is called only from provider/Layers/ClaudeProvider.ts (the health probe) and ClaudeAdapterV2.testkit.ts. The V2 adapter, which runs real turns, never calls it.
Result: the SDK query exits immediately. finalizeActiveTurnAfterQueryExit records a transport_error with the default message "Provider turn failed.", and the underlying spawn error only reaches the orchestration-v2.claude-query-stream-failed warning log. The desktop app does not persist that log. The provider status check still reports Claude as healthy, because the probe path does resolve the executable.
This is a regression for every Windows user with an npm-installed Claude Code and the default binaryPath, starting when their threads move to orchestration v2.
Suggested fix: resolve config.binaryPath with resolveClaudeSdkExecutablePath in the V2 adapter layer (around ClaudeAdapterV2.ts:7664) before handing it to makeClaudeAdapterV2. Also consider surfacing the squashed cause in the failure message; #14969 may cover that part.
Steps to reproduce
- On Windows, install Claude Code via npm (
npm i -g @anthropic-ai/claude-code), soclaudeon PATH is the%APPDATA%\npm\claude.cmdshim. - Leave the Claude provider's binary path at the default
claude. - Install desktop nightly
0.0.46-nightly.20261003.2610and send a message in a new or existing Claude thread. - The turn fails about 1.4 s later with "Provider turn failed." No incoming SDK events are logged after
query.openandprompt.offer.
Spawn check outside T3 (Node 24, no shell), same machine:
spawn('claude', ['--version'])→ENOENTspawn('<npm>\\node_modules\\@anthropic-ai\\claude-code\\bin\\claude.exe', ['--version'])→2.1.288 (Claude Code)
Version
0.0.46-nightly.20261003.2610 (desktop, Nightly channel; commit 8ed276c)
Environment
Windows 10 Pro 19045 x64, Node 24.11.1, Claude Code 2.1.288 (npm global install), desktop app with local server
Evidence
# provider event log, every post-upgrade Claude turn (outgoing only, nothing incoming):
[2026-10-03T03:44:54.298Z] NTIVE: {"provider":"claudeAgent","protocol":"claude-agent-sdk.query", ... "payload":{"type":"query.open","options":{"model":"claude-opus-5-5[1m]", ... "pathToClaudeCodeExecutable":"claude", ...}}}
[2026-10-03T03:44:54.298Z] NTIVE: { ... "payload":{"type":"prompt.offer", ... }}
# statev2.sqlite, orchestration_v2_projection_turn_items (same turn):
"type":"error","title":"Provider error","failure":{"class":"transport_error","message":"Provider turn failed.","code":null,"retryable":null}
# provider turn started 03:44:54.286Z, failed 03:44:54.299Z
# orchestration_v2_projection_runs since upgrade: 5 x claudeAgent "failed" (old and brand-new threads), codex "running" normallyRelated issues
#3740 fixed the same problem for the v1 Claude path, and the V2 adapter doesn't use that fix. #13471 made V2 expand ~ in the binary path but did not add PATH/shim resolution. #14969 (open) shows provider failure reasons but would not fix the spawn. I found no existing issue for this.
Fix applied or workaround
Set the Claude provider's binary path (Settings → Providers → Claude, i.e. providerInstances.claudeAgent.config.binaryPath in settings.json) to the absolute path %APPDATA%\npm\node_modules\@anthropic-ai\claude-code\bin\claude.exe. Confirmed: a new Claude thread then launches with that path and the turn runs normally.
Note: the server picked up the settings edit, but an existing thread whose Claude provider session was opened before the edit still launched with "claude" and failed again on retry. Existing provider sessions seem to keep the binaryPath they were created with, so affected users may need to restart the app (or start new threads) after applying the workaround.
Filed by
Claude Code (claude-opus-5-5) via t3 triage
Activity
juliusmarminge commentedon Oct 3, 2026
Note
Grok responding on behalf of Julius.
Triage
Thanks for the precise diagnosis, @MundaysDesign. The spawn check outside T3 made this easy to confirm. It's still broken on
main(43bd667), because the only commit after nightly8ed276cdoesn't touch this path.What's happening:
createClaudeAdapterV2only runsexpandHomePathonbinaryPath(around line 7671 ofClaudeAdapterV2.ts), then passes that string straight through aspathToClaudeCodeExecutable(around line 875). The default is the bare commandclaude. The Agent SDK spawns that value without a shell and without the Windows PATH/PATHEXT lookup, so an npmclaude.cmdshim never starts. The turn then ends as atransport_errorwith "Provider turn failed.", and the actual spawn error only shows up in theorchestration-v2.claude-query-stream-failedwarning.Why Claude still looks healthy:
resolveClaudeSdkExecutablePathalready handles this case. It resolvesclaudethrough PATH, then findsbin/claude.exeorcli.jsnext to the shim. But real turns never call it. Only the capabilities probe inClaudeProvider.tsand the V2 recording testkit use it. The CLI status check usesresolveSpawnCommand, which can use a shell, so Claude reports as healthy while every V2 turn fails. Codex doesn't go through this SDK option, so it isn't affected.Not a duplicate: #3740 (merged) wired that helper into the old V1 Claude session path. #13471 (merged) only expands
~for V2. The open PR #14969 improves the failure text when a session fails to open, but it doesn't resolve the executable or touch this query-exit path.Fix direction: in
createClaudeAdapterV2, resolveconfig.binaryPathwithresolveClaudeSdkExecutablePathbefore callingmakeClaudeAdapterV2. Then add a case for a bareclaudeon Windows to the existing executable-path test.Workaround: setting the absolute path you described works. A session that's already open keeps the runtime it was created with (
ProviderSessionManagerreusesexisting.exposedRuntime), so after changing the binary path, start a new thread or restart the app.