fix(container): mount per-agent .cursor dir read-write #1037

Merged
charles merged 2 commits from fix/cursor-mount-rw into main 2026-05-10 11:47:39 +00:00
Collaborator

cursor-agent writes per-workspace state under ~/.cursor/projects/<name>/ on every dispatch. The mount in PR #1032 used :ro, so the very first in-container call failed with:

Error: ENOENT: no such file or directory, mkdir '/home/claude/.cursor/projects/<workspace>'

Combined with the silent exec-fail in cursor-cli-adapter (#1036), the failure surfaced as done — task completed with empty output — third PR #1032 follow-up after #1035 (Dockerfile bundle).

Fix

apps/server/src/infrastructure/container/container-reconcile.ts:424 — switch ${credsDir}/cursor:/home/claude/.cursor:ro to :rw.

The operator-managed mcp.json is re-rendered before every dispatch by renderForInstance (called from registry.ts:414), so any in-container mutation gets overwritten on the next run — the original "buggy run can't clobber operator-managed config" concern is preserved by the render path, not the mount mode.

Verified

$ docker exec claude-hooks-dev sh -c '/usr/local/bin/cursor-agent -p ...'
Error: ENOENT … mkdir '/home/claude/.cursor/projects/tmp'   ← before
<stream-json output>                                         ← after (post container recreate)

Apply

After merge: just containers-rebuild to recreate the 12 containers with the new mount mode.

Refs: #1032, #1035, #1036.

cursor-agent writes per-workspace state under `~/.cursor/projects/<name>/` on every dispatch. The mount in PR #1032 used `:ro`, so the very first in-container call failed with: ``` Error: ENOENT: no such file or directory, mkdir '/home/claude/.cursor/projects/<workspace>' ``` Combined with the silent exec-fail in cursor-cli-adapter (#1036), the failure surfaced as `done — task completed` with empty output — third PR #1032 follow-up after #1035 (Dockerfile bundle). ## Fix `apps/server/src/infrastructure/container/container-reconcile.ts:424` — switch `${credsDir}/cursor:/home/claude/.cursor:ro` to `:rw`. The operator-managed `mcp.json` is re-rendered before every dispatch by `renderForInstance` (called from `registry.ts:414`), so any in-container mutation gets overwritten on the next run — the original "buggy run can't clobber operator-managed config" concern is preserved by the render path, not the mount mode. ## Verified ``` $ docker exec claude-hooks-dev sh -c '/usr/local/bin/cursor-agent -p ...' Error: ENOENT … mkdir '/home/claude/.cursor/projects/tmp' ← before <stream-json output> ← after (post container recreate) ``` ## Apply After merge: `just containers-rebuild` to recreate the 12 containers with the new mount mode. Refs: #1032, #1035, #1036.
fix(dockerfile): keep cursor-agent bundle, symlink to /usr/local/bin
All checks were successful
qa / dockerfile (pull_request) Successful in 14s
qa / i18n-string-check (pull_request) Successful in 14s
qa / sql-layer-check (pull_request) Successful in 14s
qa / db-schema (pull_request) Successful in 16s
qa / qa-1 (pull_request) Successful in 3m37s
qa / qa (pull_request) Successful in 0s
b8132d1578
PR #1032's cursor block copied only the `cursor-agent` shell wrapper
to /usr/local/bin and rm'd /opt/cursor-agent. The wrapper is a bash
script that resolves SCRIPT_DIR via realpath and execs the bundled
`node` against `index.js` + the chunk files shipped alongside it. With
the bundle deleted, the script bombed with:

  /usr/local/bin/cursor-agent: line 26: /usr/local/bin/node: No such
  file or directory

Result: every dispatch on a `provider: cursor` agent failed silently —
docker exec returned non-zero, the cursor-cli-adapter saw stdout EOF
without any stream-json events, and the runner marked the task "done"
with empty output.

Install the unpacked bundle under /opt/cursor-agent and expose
`/usr/local/bin/cursor-agent` as a symlink so realpath() resolves back
to the bundle dir; chmod 0755 the wrapper + node so the bundled
interpreter is executable.
fix(container): mount per-agent .cursor dir read-write so cursor-agent can write project state
All checks were successful
qa / sql-layer-check (pull_request) Successful in 12s
qa / dockerfile (pull_request) Successful in 12s
qa / db-schema (pull_request) Successful in 14s
qa / i18n-string-check (pull_request) Successful in 17s
qa / qa-1 (pull_request) Successful in 3m31s
qa / qa (pull_request) Successful in 0s
0578f255a3
cursor-agent writes per-workspace state under `~/.cursor/projects/<name>/`
on every dispatch. The mount in PR #1032 used `:ro`, so the very first
in-container call failed with:

  Error: ENOENT: no such file or directory, mkdir
    '/home/claude/.cursor/projects/<workspace>'

Combined with the silent exec-fail in cursor-cli-adapter (#1036), the
failure surfaced as `done — task completed` with empty output.

Switch the mount to `:rw`. The operator-managed `mcp.json` is re-rendered
before every dispatch by `renderForInstance` (called from
`registry.ts:414`), so any in-container mutation gets overwritten on the
next run — the original "buggy run can't clobber operator-managed
config" concern is preserved by the render path, not the mount mode.
charles deleted branch fix/cursor-mount-rw 2026-05-10 11:47:39 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks!1037
No description provided.