Repository navigation
[Bug]: Cursor thread fails to start with ProviderAdapterSessionClosedError when project .cursor/cli.json contains deprecated keys — detect and offer auto-migration #12451
Description
Activity
- addedbugSomething is broken or behaving incorrectly.Something is broken or behaving incorrectly.needs-triageIssue needs maintainer review and initial categorization.Issue needs maintainer review and initial categorization.
on Sep 18, 2026 Triage
Confirmed on current
main(9ea9c3d5d). Still present vs the reporter’s desktop0.0.42. Real Cursor ACP startup bug: the child prints an actionable schema error and exits 1; T3 drains that stderr and surfaces onlyACP process exited with code 1. Not a duplicate of #4380 / #12224, #5943, or #10480. No open PR fixes this.What happens
A turn starts
ensureSessionForThread→ProviderService.startSession→ Cursor adaptermakeCursorAcpRuntime→acp.start(). Spawn iscursor-agent acpwithcwd= the thread workspace, so currentcursor-agentreads.cursor/cli.jsonbefore ACPinitialize. On unrecognized keys it writes the schema error to stderr and exits 1.makeCursorAcpRuntimenever setsonStderr. The shared ACP runtime still reads the pipe (so it does not back-pressure) and then throws the text away.effect-acpalso drains child stderr at the stdio layer and builds the exit error with onlycode/pid.AcpProcessExitedErrorhas no stderr field.mapAcpToAdapterErrorthen wraps that exit as a session-closed error whose own message is only the thread id.That matches the pasted stack (
processTurnStartRequested→ensureSessionForThread→startSession→mapAcpToAdapterError).formatFailureDetailpretty-prints that cause chain and never had a stderr excerpt to include. T3 does not read.cursor/cli.jsonanywhere.The reporter’s manual repro is the right one:
cursor-agent --versionin the project cwd exits 1 with the schema text; the same binary outside that cwd is fine. Workaround (moveapprovalMode/sandboxout ofcli.json) is correct and is a user-side config migration, not a T3 fix.Not a duplicate
- Git command errors discard stderr, making failures opaque to callers #4380 / [Bug]: PullRequestSyncReactor's gh calls fail with an opaque error — VcsProcessExitError discards stderr, so the real cause can't be diagnosed #12224 — same class (child stderr dropped so the failure is opaque), but
git/gh/VcsProcessExitError. Different process and error type. - [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 — Claude/Codex startup death on macOS TCC
EPERM, then the first RPC name (setPermissionMode) is blamed. Different providers, different mask. Those traces already carry astderr:line; this ACP path does not. - Cursor provider fails through local ACP with internal max-retries error #10480 — Cursor ACP
initialize/authenticate/session/promptsucceed, then a mid-turnmax-retries/ transport diagnostic. Session starts. This issue never reachesinitialize.
Closest open work is #9839 (Antigravity-only): it adds bounded startup-diagnostic plumbing on
AcpSessionRuntimeand wires it for Antigravity install validation. It does not attach stderr to Cursor / GrokAcpProcessExitedError, and it does not changemapAcpToAdapterError. #12337 recycles Cursor sessions after a transport failure on an already-started session. Neither is a fix here.Fix direction (must-fix first)
- Keep a bounded, redacted tail of ACP child stderr (last few KiB; same secret-hygiene as Antigravity — no tokens, pairing URLs, or home paths).
- On early process death, wait briefly so that tail is available, then put a sanitized excerpt on
AcpProcessExitedError(or a dedicated startup-failure error). - Map that to a user-facing adapter error whose
detailis the excerpt, notcursor adapter thread is closed: <uuid>. Do this for every ACP driver (Cursor and Grok share the hole today; Antigravity is the only one with anonStderrhook, and that hook is for auth URLs). - Prove it with a fake
cursor-agentthat writes the reporter’sunrecognized_keyspayload and exits 1 before ACPinitialize. Assert the activity / lastError text containscli.json/Unrecognized keyand not onlyexited with code 1.
Out of scope for the first PR
Auto-parsing
.cursor/cli.jsonand offering a one-click migrate to.cursor/permissions.json/.cursor/sandbox.json. T3 should not rewrite project Cursor config without an explicit product decision; that schema will keep moving. Surfacing the native stderr turns this into a 5-second manual fix (the reporter’s workaround) for every current and futurecursor-agentrejection.Workaround until then: remove the rejected keys from the project
.cursor/cli.json(move approval/sandbox into the sibling files the current CLI reads). Cursor turns start immediately.- Git command errors discard stderr, making failures opaque to callers #4380 / [Bug]: PullRequestSyncReactor's gh calls fail with an opaque error — VcsProcessExitError discards stderr, so the real cause can't be diagnosed #12224 — same class (child stderr dropped so the failure is opaque), but
- addedacceptedfeature request acceptedfeature request acceptedvia-triageFiled through npx t3 triageFiled through npx t3 triageand removedneeds-triageIssue needs maintainer review and initial categorization.Issue needs maintainer review and initial categorization.
on Sep 18, 2026 Taking this — will detect deprecated
.cursor/cli.jsonkeys on Cursor ACP startup and surface/migrate instead of a opaque ProviderAdapterSessionClosedError.
Before submitting
Area
apps/server
Steps to reproduce
{
"approvalMode": "unrestricted",
"sandbox": { "mode": "disabled", "networkAccess": "allow_all" },
"permissions": { "allow": ["Shell(**)"] }
}
Expected behavior
Two layers of grace:
If the cursor-agent subprocess fails at startup, T3 Code should capture its stderr and surface it. cursor-agent prints a precise, actionable schema error before exiting ("Invalid project config at .../.cursor/cli.json: Unrecognized key(s): 'approvalMode', 'sandbox'") — the user should see that, not "ACP process exited with code 1".
Better still: T3 Code already knows the workspace root and spawns the provider — it's in a position to parse .cursor/cli.json itself (or read the provider's stderr), recognize known deprecated/invalid keys, and offer a one-click "auto-fix" for known migration paths. In this concrete case the current cursor-agent moves these settings to sibling files the CLI already reads:
A prompt like "Deprecated keys found in .cursor/cli.json — migrate to current format?" would turn a hard, inscrutable failure into a 5-second fix, and would generalize to future cursor-agent schema changes (and other ACP providers with similar config drift).
Actual behavior
T3 Code spawns
cursor-agent acpwith cwd = the project directory. cursor-agent validates the project config on startup, finds unrecognized keys, prints the validation error to stderr, and exits 1. The adapter reports only:ProviderAdapterSessionClosedError: cursor adapter thread is closed:
[cause]: AcpProcessExitedError: ACP process exited with code 1
The stderr is dropped, so the actionable message never reaches the user. Reproduced from a shell:
cursor-agent --versioninside the project exits 1 with the schema error; from any other directory it exits 0 (cursor-agent 2026.09.15-d2fe57e). Net effect: a routine cursor-agent auto-update silently bricks every Cursor thread in the workspace, with an error message that points at T3 Code rather than the config file.Impact
Major degradation or frequent failure
Version or commit
T3 Code (Alpha) desktop 0.0.42
Environment
macOS 27.0 (26A428), Apple Silicon T3 Code (Alpha) 0.0.42 desktop Provider: Cursor (ACP), cursor-agent 2026.09.15-d2fe57e at ~/.local/share/cursor-agent/
Logs or stack traces
ProviderAdapterSessionClosedError: cursor adapter thread is closed: 45515176-6c8b-414b-9aba-4617c2e2703c at mapAcpToAdapterError (.../apps/server/dist/bin.mjs:145221:45) at startSession (.../apps/server/dist/bin.mjs:203454:59) at ensureSessionForThread (.../apps/server/dist/bin.mjs:203373:33) at processTurnStartRequested (.../apps/server/dist/bin.mjs:203752:36) at processDomainEvent (.../apps/server/dist/bin.mjs:204127:46) { [cause]: AcpProcessExitedError: ACP process exited with code 1 at Object.onSuccess (.../apps/server/dist/bin.mjs:108047:23) } Underlying process stderr (recovered by running cursor-agent manually in the same cwd): Invalid project config at /Users/<user>/Projects/<repo>/.cursor/cli.json: schema validation failed. [ { "code": "unrecognized_keys", "keys": ["approvalMode", "sandbox"], "path": [], "message": "Unrecognized key(s) in object: 'approvalMode', 'sandbox'" } ]Screenshots, recordings, or supporting files
No response
Workaround
Manually remove the rejected keys from the project's .cursor/cli.json; move approval/sandbox settings into .cursor/permissions.json and .cursor/sandbox.json respectively. Cursor turns start working immediately. (This manual fix is exactly what the proposed auto-migration prompt should do for the user.)