fix(ai): isolate cursor agent config dirs for parallel execution#440
fix(ai): isolate cursor agent config dirs for parallel execution#440janhesters wants to merge 1 commit intomasterfrom
Conversation
…ndition When multiple Cursor CLI agents run concurrently, they all race on the same ~/.cursor/cli-config.json.tmp file during atomic config writes. One process renames the .tmp file while another tries to do the same, causing ENOENT crashes. Fix: set a unique CURSOR_CONFIG_DIR (via cuid2) per spawned cursor agent process so each gets its own config directory. Only applies to cursor agents — claude and opencode are unaffected.
ianwhitedeveloper
left a comment
There was a problem hiding this comment.
Nice, focused fix — correct for the documented race and tests pass locally (238/238). Requesting a couple of changes before merge; the rest are nits.
Must-fix
1. Temp dirs are never cleaned up. Each spawn creates tmpdir()/riteway-cursor-<cuid> and nothing removes it. macOS purges /var/folders/... only on reboot, and Linux often never cleans /tmp. CI workers and dev machines running riteway ai repeatedly will leak directories indefinitely. Please add a best-effort await rm(dir, { recursive: true, force: true }) in a finally block in runAgentProcess.
Should-fix
2. Cursor detection by literal command string is fragile.
Why this matters and a suggested shape
const isCursorAgent = (command) => command === 'agent';A user-supplied registry (riteway.agent-config.json) or --agent-config file can use any command — /usr/local/bin/agent, cursor-agent, a wrapper script — and silently lose isolation. Conversely, any unrelated tool literally named agent would get a spurious CURSOR_CONFIG_DIR.
The PR description acknowledges Claude Code and Gemini CLI have the same class of bug, so the mechanism wants to be generic. Move the knowledge into agent-config.js:
cursor: {
command: 'agent',
args: ['--print', '--output-format', 'json'],
outputFormat: 'json',
isolateConfigEnv: 'CURSOR_CONFIG_DIR',
isolateConfigPrefix: 'riteway-cursor-'
}Then buildSpawnOptions(agentConfig) becomes generic and self-documenting, and the agentConfigFileSchema gains an explicit opt-in field for custom registries.
3. Verify (or guarantee) the dir exists. The code points CURSOR_CONFIG_DIR at a path that doesn't exist on disk. Works only if the Cursor CLI mkdir -ps it before writing .tmp. Either add a comment documenting the assumption against a specific cursor-cli version, or mkdirSync(dir, { recursive: true }) before spawn to be defensive.
Nits
Test assertions could be tightened
-
'does not set CURSOR_CONFIG_DIR for non-cursor agents'asserts onoptions?.env?.CURSOR_CONFIG_DIR === undefined. The actual contract today is that the third arg isundefinedentirely. Stronger:actual: options, expected: undefined
-
'passes a unique CURSOR_CONFIG_DIR env to each cursor agent spawn'only type-checksdir1. Ifdir2wereundefined,dir1 !== dir2would still betrueand mask the bug. Type-check both.
Docs
executeAgentJSDoc doesn't mention the implicit env mutation when the command is cursor — a one-liner note (or a comment nearbuildSpawnOptionsciting the upstream Cursor CLI bug) would help future readers.- No
RELEASING.md/ changelog entry; this unblocks parallel--agent cursorruns and probably deserves a note.
Out of scope but worth noting
No new untrusted input; spreading process.env into the child is appropriate; trust boundary documented in agent-config.js is preserved. No OWASP concerns from this diff.
|
Forgot to mention: manual testing corroborates the fix — the |
Summary
~/.cursor/cli-config.json.tmpduring atomic config writes, causingENOENTcrashes onrenameCURSOR_CONFIG_DIR(using cuid2) pointing to an isolated temp directory, eliminating the raceRoot cause
The Cursor CLI writes config atomically via
cli-config.json.tmp→cli-config.jsonrename. With concurrent processes, one process completes the rename while another tries to rename the same.tmpfile that no longer exists. This is the same class of bug reported in Gemini CLI and Claude Code.Test plan
CURSOR_CONFIG_DIRCURSOR_CONFIG_DIRvaluesCURSOR_CONFIG_DIRset