[Bug]: "turn/setPermissionMode failed" masks provider CLI startup death — macOS TCC EPERM kills both Claude and Codex when project is in a protected folder#5943

openedAug 10, 2026byYyossi837

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/server

Steps to reproduce

On macOS:

  1. Have a project inside a TCC-protected folder (~/Desktop/..., ~/Documents/..., ~/Downloads/...)
  2. Remove T3 Code's Files-and-Folders permission — or simply delete + re-download the app, which can invalidate the existing grant silently (macOS shows no re-prompt)
  3. Send any prompt in any thread, with any provider

Expected behavior

  1. When the provider CLI process exits before/during the control-protocol handshake, the UI error should surface the child's stderr and exit code, not the name of the first RPC that happened to fail against the dead process.
  2. On macOS, when the spawn fails with EPERM and the project path is under Desktop/Documents/Downloads, show actionable guidance ("grant T3 Code access in System Settings → Privacy & Security → Files and Folders").

Actual behavior

  • Claude threads fail with Provider turn start failed — turn/setPermissionMode failed and a "Claude runtime stream failed." banner
  • Codex threads fail with ProviderAdapterProcessError: ... Codex App Server process exited with code 1
  • Neither error mentions the real cause. The truth is only in ~/.t3/userdata/logs/server.trace.ndjson:
1[cause]: Error: Claude Code process exited with code 1.
2stderr: error: An internal error occurred (EPERM)

The spawned CLI dies instantly on startup (macOS TCC denies file access under the protected folder for child processes of the app), and the first control request (set_permission_mode) fails against a dead process — so the user-facing error blames permission modes. This sends users down entirely the wrong debugging path (reinstalling the app, changing the permission-mode dropdown, etc.), and I suspect some existing reports of this error tag (e.g. #4495) are actually this failure mode misdiagnosed.

Verified during diagnosis: the same pinned CLI binary, run from Terminal in the same project directory, works perfectly — including every set_permission_mode value (default, acceptEdits, plan, bypassPermissions, auto) sent over the stream-json control protocol. The failure only occurs when the CLI is spawned as a child of T3 Code without the folder grant.

Impact

Major degradation or frequent failure

Version or commit

0.0.32

Environment

macOS (Darwin 25.5), T3 Code 0.0.32 desktop app, claude CLI 2.1.223 (SDK-pinned, @anthropic-ai/claude-agent-sdk 0.3.223), codex-cli 0.147.0. Both providers fail identically; both work standalone from Terminal.

Workaround

1tccutil reset All com.t3tools.t3code

then relaunch the app and re-grant folder access when prompted. Fixes both providers immediately.

Synced from GitHub

I ran into the same symptoms, but the root cause was that mise did not trust the mise.toml file a agent copied into a new repo. The name generation model then failed to start because mise aborted starting the cli.

Synced from GitHub

Just ran into this issue myself and the work around shared here worked for me 😮‍💨

Was almost forced to use Claude Desktop 😅

Synced from GitHub

Note: drafted with Claude Code on the affected machine, from its local logs.

Another real-world case on 0.1.2-nightly (Claude provider, macOS), plus two related spots that #22763 does not touch yet.

Environment: T3 Code (Nightly) desktop 0.1.2-nightly.20261021.3486 (also seen on .3473), macOS 26 (Darwin 25.6.0) arm64, Claude Code 2.1.306, behind a TLS-inspecting corporate proxy.

What happened: the Claude CLI exited with code 1 about 10.9 s after spawn, before emitting any SDK frame. Reproducing the same startup by hand (same binary, same environment) showed this on stderr:

1Unable to verify organization for the current authentication token ... This may be a network error

In T3 the turn showed only Provider turn failed. (class: "transport_error", code: null). The stderr text was not kept anywhere, and no orchestration-v2.claude-query-stream-failed entry with a cause showed up in the server traces. On main @ e78f580468:

  • spawnClaudeProcessGroup spawns with stdio: ["pipe", "pipe", "ignore"] (apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts:328).
  • The failure is built with makeProviderFailure({ cause, class: "transport_error" }) (L6955).
  • ClaudeAgentSdkQueryRunnerError is not in PROVIDER_FAILURE_CATEGORIES, so the message falls through to DEFAULT_PROVIDER_FAILURE_MESSAGE (packages/provider-core/src/server/failure.ts).

#22763 fixes the main gap (stderr tail and exit code on the turn failure). Two related spots it leaves as they are:

  1. The MCP warning points at the wrong cause. When the CLI dies during startup, queryRuntime.setMcpServers(mcpServers) rejects. Effect.orElseSucceed(() => Object.keys(mcpServers)) (ClaudeAdapterV2.ts:956) then reports every configured server as failed. As a result, orchestration-v2.claude-mcp-servers-unavailable is the most specific warning in the log, and it sends debugging toward MCP configuration while the real cause is the dead CLI. Suggestion: when the rejection comes from the process exiting, log that (or skip this warning) instead of listing every server.
  2. The capabilities probe also drops stderr. apps/server/src/provider/ClaudeProvider.ts:240 passes stderr: () => {} to the probe query. Auth and org-verification failures like the one above can show up there too, so provider status loses the same diagnostic. A bounded, redacted tail, using the same formatter as #22763, would help there as well.

Workaround until then: run the configured claude binary by hand with the same environment to see its stderr.