ADR 0029: Code page edit sessions with a terminal
Context
Section titled “Context”The Code page lets the owner or an admin save one file of the live tree as a human Change
(code-edit.ts). A real fix often changes more than one file, adds or deletes a file, or needs a
command such as bun test before the person sends it. These rules apply:
- A person never advances canonical directly. Only verified Evidence advances the Canonical State.
- The Sandbox takes host time. The Project has one Sandbox (
proj-<uuid>) that agents and Candidate checks share (ADR 0011). The monthly meterSANDBOX_MONTHLY_SECONDSlimits it to 72,000 s (20 hours) by default. - The Sandbox never gets
INTERNAL_TOKEN,GIT_ADMIN_TOKEN,SANDBOXD_TOKEN, or a model credential.
Decision
Section titled “Decision”- Only the owner and the admins (
can(role, "code.edit")) open a session. Each person has at most one open session per Project (unique indexcode_sessions_one_open). A second open returns the open session. A session belongs to the person who opened it. Another person, also an admin, gets 404 for it. Everyone else uses the read-only Code page. - The API module (
workers/api/src/code-sessions.ts) checks the role and the owner of the session on each request. It calls the integration module through the in-processINTEGRATIONbinding (RPC). The integration module (workers/integration/src/code-sessions.ts) owns the session state, the fork, and the Sandbox. It checks the owner and the session state again from the product database. - Open: the session forks canonical into the git service repository
edit-<hex>for sessionedit_<hex>. Cleanup derives the name only from the database row ID. The open does not start the Sandbox. - Edits: the table
code_session_filesholds each changed file (its full text) or a deleted path. Reads come from the edits, then from the code store of the base tree in the object store, then from the git service. A file path is under the repository root, has no..part, and has no.gitpart at any depth. A file is text (no NUL byte) and at most 256 KB. A binary file is refused with 415, a large file with 413. A session holds at most 200 changed paths. - Terminal: the first command starts the Project Sandbox. The session checkout is
/workspace/edit-<hex>. Root clones the fork with a read token in its own environment and revokes the token. Each command then runs:- as an unprivileged user that is unique to the session (
setpriv, no groups, no new privileges), with a clean environment (PATH, a sessionHOME). No token and no secret is in it. It cannot change agent or Candidate checkouts. - with the network policy of the Project Sandbox, the same as agent commands.
- with a 120 s timeout. A timeout returns exit code 124.
- with each output stream cut to its last 64 KB.
After each command, a kill as the session user stops every process of the session. Every file
that differs from the base becomes a session edit. A binary file, a file over 256 KB, or an
invalid path stays out, and a note in
stderrnames it.
- as an unprivileged user that is unique to the session (
- Meter: each command reserves 480 s on
usage_counters(sandbox-seconds) before the Sandbox starts. At the cap, the command is refused with 429 and reasonsandbox_cap. After the command, the meter settles to the awake time that the command added (its time plus a 120 s idle tail). Commands within one tail pay the overlap once. - Audit:
code_session_commandsrecords each command, its folder, exit code, duration, and time. It never records the output. - Submit: all edits become one Change with one human ChangeSet through
recordHumanChange, the path of the single-file save with several files. The commit is in the human workspace forkws-<k>. The Change then composes, verifies, previews, and releases as an agent Change does. If canonical advanced and did not change an edited file, the submit moves to the current base. If canonical changed an edited file, the submit is refused with 409stale_base. A repeat finds the same Change. - End: discard, submit, 10 minutes without a request (
CODE_SESSION_IDLE_MS), 2 hours after the open (CODE_SESSION_MAX_MS), or an archived Project. Each request checks the limits first, and the daily maintenance sweep (forks.sweep) ends overdue sessions. The end deletes the fork and the edits. Edits that are not submitted are discarded. The web warns 1 minute before the idle limit. - Sandbox stop: while the Sandbox is live, the end kills every process of the session and
removes its directory. sandboxd then removes the Sandbox after 2 idle minutes (
sleepAfter). A removed Sandbox has no disk, so the end does not start it. The end does not destroy the Sandbox, because agents and Candidate checks of the Project can use it at the same time. - Events:
code.session.opened,code.session.closed(withreason:discarded,idle,max_age, orarchived), andcode.session.submitted(withchangeId). They are informational for the causal router. - Project cleanup deletes the
edit-*forks,code_session_files,code_session_commands, andcode_sessions.
Alternatives considered
Section titled “Alternatives considered”| Alternative | Why it lost |
|---|---|
| Keep the edits only in the Sandbox checkout | sandboxd removes the Sandbox and its disk after 2 idle minutes. A session lives up to 10 idle minutes. |
| A separate Sandbox instance for each session | Each Project has one Sandbox (ADR 0011), and SANDBOX_MAX allows 4 live sandboxes on the host. A second instance doubles the Sandbox hours. |
| Destroy the Sandbox when a session ends | It stops agent runs and Candidate checks that use the same Sandbox. The 2-minute idle removal stops it. |
| Run commands as root with an allowlist, as agents do | A person needs any command in a terminal. An unprivileged user per session limits what a command can reach. |
| Commit each save to the fork | Each save then costs a commit and a push to the git service. One commit at submit is enough. |
Consequences
Section titled “Consequences”- An edit session uses the same monthly Sandbox meter as agents and Candidate checks. At the cap, the terminal is off, and file edits and submit still work.
- A command can reach the network that the Sandbox allows. It cannot read the tokens of agents or Candidate checks, because those are in root process environments only.
- A session that a person leaves open ends at its next request or at the next daily sweep. Its edits in the database stay until then. sandboxd removes its Sandbox 2 minutes after the last command.