Repository navigation
Align Environment Files.create write semantics - #76
Merged
Merged
Conversation
The shared workspace installer gains an explicit per-call mode. The new create mode walks existing components with the held O_PATH|O_NOFOLLOW walk, creates missing parents with mkdirat (mode 0700) only after the complete body is verified, reopens each one without following links, and installs with renameat2(RENAME_NOREPLACE), falling back to linkat plus unlinking the staging name. An existing file, a directory, a symlink or a non-directory component rejects with a fixed code and nothing is installed. The four-argument form keeps the replace mode used by initial Session files and Skills, including Core-issued installs on older Runtime images.
The local workspace writer always selects the helper's create mode. Its new directory and unsafe-destination codes become known rejections that the daemon sends as write_rejected with an optional reason; the gateway accepts a reason only on that rejection, and older peers ignore it.
Inline data above 5 MiB decoded returns the official 400 before any Runtime work; file_id copies keep the 50 MiB destination bound. The Worker maps the installer's destination reasons to the official conflict and symlink or overwrite messages. Core keeps no path ledger, so an existing file written by an earlier Files.create uses the untracked message. Rejections settle their durable intent as rejected and consume no Source File.
The client's strict Base64 check now scans with a flat character class, since the grouped pattern overflowed the regular-expression stack near 4 MiB and blocked an exact 5 MiB upload.
Record rows FW1-FW11 in the Environment Files contract with the official evidence, the installer modes and the tracked/untracked message difference, register the FW evidence code and update row 27, the coverage summary, the executor README and the contributor rules.
The daemon's native-helper case runs only when a built helper is supplied. A normal rejection leaves nothing; only a race, an I/O error or a device mismatch after parent creation can leave empty mode-0700 directories.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Environment Files.create now follows the observed official write semantics. It creates missing parent directories, never overwrites an existing path, and bounds inline data at 5 MiB decoded. Symlink safety is unchanged, and initial Session files, Skills, Plugins and cold resume keep their install behavior. The pinned baseline is unchanged (SDK 3.13.0 / d7c41ef /
agents=v1).Behavior
mkdirat, mode 0700, under the held no-follow directory walk. This happens only after the body digest is verified. An existing symlink or non-directory component still rejects.invalid_request_error, param null, with "environment.files paths must not traverse symlinks or overwrite existing files".renameat2(RENAME_NOREPLACE), with alinkatfallback. Neither can replace a destination, even under races.dataabove 5 MiB decoded returns 400 with "environment.files[0].data exceeds the 5 MiB decoded limit". This is checked before the provisioning check, the Source File lookup and any Worker work.file_idcopies keep the 50 MiB bound.createargument to the write helper, used by the daemon local workspace writer for both Core-managed Docker and self_hosted Runtimes.invalid_input.reasonfield.Evidence
environment-files.md,operation-evidence.md(register FW, row 27),official-semantics-alignment.mdandenvironment-templates.md.Validation
Live acceptance on Core-managed Docker
openai_hostedwith Codex. Each phase's Core, daemon and helpers were built from the same tree and checked by sha256 inside the Runtime container. No model Turns.setup_commands/tmp/tmp/x.txtabsentfile_idcopy of about 6 MiBRENAME_NOREPLACEis supported on the Runtime volume filesystem; a probe returnedEEXIST. Cleanup and secret scans passed.Tests:
Server gate: all
make checktargets plus Web typecheck, core-doctor, unit tests and build pass. The Playwright browser cases were not run on the server (no Google Chrome; the user approved the skip).make openapiis byte-identical.Independent blind review by a fresh Claude Code subagent (the user-approved replacement for GPT-6 Astra). It found no blockers and confirmed:
Its documentation follow-ups are applied in the last commit, verified by focused tests and the gate without a new review, per the user's rule.
Runtime images (Codex, Claude and MiniMax Docker images, the E2B template and user-managed bundles) must be rebuilt for the new write semantics. No full protocol compatibility is claimed.
Need help on this PR? Tag
@codesmithwith what you need. Autofix is disabled.