Agents cannot write scratch files in production: /tmp/opencode is owned by root #36

Closed
opened 2026-09-15 01:32:31 -04:00 by cmoriarty · 1 comment
Owner

An agent in production tried to write /tmp/opencode/ws-test.mjs and got PermissionDenied: FileSystem.writeFile, from the Write tool and from bash.

opencode's own temp directory is os.tmpdir()/opencode, and its default permissions let agents write there without asking (verified in 1.18.21: external_directory allows Path.tmp/*). The image build created /tmp/opencode as root (drwxr-xr-x root root, present in :latest before any container starts), and runs execute as braid, so opencode's sanctioned scratch space is unwritable. /tmp/node-compile-cache is left root-owned the same way.

Opening all of /tmp is not the fix: every run in every slot shares the container's /tmp, so runs would collide and read each other's files, and nothing cleans it.

Proposed:

  • Each run's opencode server gets its own TMPDIR under the run directory, outside the worktree. opencode's scratch directory, mktemp, pytest and Node all follow it, and it goes away with the run.
  • The image build leaves nothing in /tmp.
  • Braid's rejection of an out-of-repo path names the run's scratch directory.
An agent in production tried to write `/tmp/opencode/ws-test.mjs` and got `PermissionDenied: FileSystem.writeFile`, from the Write tool and from bash. opencode's own temp directory is `os.tmpdir()/opencode`, and its default permissions let agents write there without asking (verified in 1.18.21: `external_directory` allows `Path.tmp/*`). The image build created `/tmp/opencode` as root (`drwxr-xr-x root root`, present in `:latest` before any container starts), and runs execute as `braid`, so opencode's sanctioned scratch space is unwritable. `/tmp/node-compile-cache` is left root-owned the same way. Opening all of `/tmp` is not the fix: every run in every slot shares the container's `/tmp`, so runs would collide and read each other's files, and nothing cleans it. **Proposed:** - Each run's opencode server gets its own `TMPDIR` under the run directory, outside the worktree. opencode's scratch directory, `mktemp`, pytest and Node all follow it, and it goes away with the run. - The image build leaves nothing in `/tmp`. - Braid's rejection of an out-of-repo path names the run's scratch directory.
Author
Owner

Fixed in a4f4ac7 (archived as runs-have-their-own-scratch-directory).

  • Per-run TMPDIR: each run's opencode serve gets TMPDIR=<run>/tmp, created before it starts and written last so a caller's TMPDIR cannot win. opencode's scratch directory, mktemp, tempfile, pytest and Node follow it, runs no longer share /tmp, and it is reclaimed with the run.
  • Clean image: the Dockerfile's last root step empties /tmp, and braid-selfcheck fails if the image leaves anything there.
  • Rejection message: Braid's rejection of an out-of-repo permission request names $TMPDIR and the run's path.
  • All of /tmp stays out of bounds.

On production after the deploy: the self-check passed, no root-owned entries in /tmp, and the run's server created runs/run_01M2HF4SNRBVJ6TA8ZX11T2Z4H/tmp/opencode owned by braid at startup.

Fixed in a4f4ac7 (archived as `runs-have-their-own-scratch-directory`). - **Per-run TMPDIR:** each run's `opencode serve` gets `TMPDIR=<run>/tmp`, created before it starts and written last so a caller's `TMPDIR` cannot win. opencode's scratch directory, `mktemp`, `tempfile`, pytest and Node follow it, runs no longer share `/tmp`, and it is reclaimed with the run. - **Clean image:** the Dockerfile's last root step empties `/tmp`, and `braid-selfcheck` fails if the image leaves anything there. - **Rejection message:** Braid's rejection of an out-of-repo permission request names `$TMPDIR` and the run's path. - All of `/tmp` stays out of bounds. On production after the deploy: the self-check passed, no root-owned entries in `/tmp`, and the run's server created `runs/run_01M2HF4SNRBVJ6TA8ZX11T2Z4H/tmp/opencode` owned by `braid` at startup.
Sign in to join this conversation.
No labels
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
cmoriarty/braid#36
No description provided.