Repository navigation
Skip output symlinks and publish only changed Artifacts - #47
Merged
Merged
Conversation
A symlink anywhere below outputs/ is now skipped by its lstat type instead of aborting the export and failing the Turn, matching the official service (HE-51). Links are never followed, opened or resolved. Hard links, FIFOs, sockets, devices, device crossings, a linked outputs root and concurrent changes keep rejecting the export. The new test proves through inotify that no link target inside or outside the workspace is opened or read.
A completed Turn no longer republishes every output (HE-52). In the Turn's terminal transaction, which holds the Session lock that also orders Artifact deletion, staged paths whose sha256 equals the newest remaining published Artifact for that path in the Session are dropped and their private objects unlinked. New paths, changed bytes and paths without a remaining Artifact are published; unchanged paths keep their existing immutable Artifact IDs. The first Turn is unchanged and no migration is needed. Real-PostgreSQL tests cover new, unchanged, changed, changed-back, deleted-then-unchanged and deleted-during-capture paths, Session scoping and private object accounting. The user-managed Runtime acceptance no longer expects unchanged outputs under later Turns.
The Artifact list now returns object, data, first_id, last_id and has_more, with null first and last IDs on an empty page, like the Session, Turn and Item lists (HE-53). Paging and cursors are unchanged. A malformed environment_id resolves to the never-assigned maximum UUID and returns an empty page instead of 400, matching another Environment's ID (HE-56); tenant and Session lookup still run first, and cursor and limit errors are unchanged. Handler and real-PostgreSQL HTTP tests cover the envelope, other, foreign and malformed filters, and foreign or missing Sessions. The shared pinned-SDK and raw HTTP verifier asserts the envelope and filter and now runs against PostgreSQL across three Turns without a model.
Add the September 23 Artifact section with rows A1-A4, the decision to settle republication in the Turn's terminal transaction, the newest-version order, the no-tombstone edge and deferred cases. Register the hosted-environment evidence as Y, update operation rows 16-19, the README ledger and the capture rules in CONTRIBUTING.
One test publishes Turn 1 with a runtime-reported completion an hour ahead, so a later Turn's Artifact has an earlier publication time, and asserts that the newest version follows Turn order in both directions. The other holds the Session lock while deleting the newest Artifact, confirms Turn completion waits on that lock, then commits and asserts the path is republished with no leaked private object. Ordering by publication time, or deciding at capture time, fails these tests.
Record comparison against the newest remaining Artifact as an accepted known difference from the batch plan's wording, with the bravo/bravo-v2 example, and list the two new regression tests. The README ledger again marks exact upstream defaults as unverified, matching OpenAPI and the handler description.
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.
Artifact capture failed a whole Turn when the outputs directory contained a symlink, and it republished every output file on every later Turn. The official service skips symlinks and publishes only new or changed paths. This batch aligns capture and the Artifact list with the first owned official Artifact observations. The pinned baseline is unchanged (SDK 3.13.0 / d7c41ef /
agents=v1).Behavior
Symlinks (A1): symlinks anywhere inside the outputs tree are skipped, whether they point to a file, to a directory, nowhere, or outside the workspace. They are never followed, opened or resolved, and every regular file is still captured, so the Turn completes. The rules for a symlinked
outputsroot, hard links, special files, device crossings and concurrent changes are unchanged, since there is no official evidence for them.Republication (A2): a later Turn publishes a path only if:
Unchanged files keep their existing Artifact IDs. The decision runs in the Turn completion transaction, under the same Session lock as Artifact deletion, so a deletion that commits during capture is honored. There is no migration: sha256 is already stored.
List envelope (A3):
{object: "list", data, first_id, last_id, has_more}, with null IDs on an empty page, like the Session, Turn and Item lists.Malformed
environment_idfilter (A4): returns 200 with an empty page. Tenant and Session checks still run first.Documented local decision: comparison is against the newest remaining Artifact. If the newest one is deleted while an older Artifact with the same bytes remains, nothing is republished. Recording deletions would need a schema change, and the official behavior for this edge is unknown.
Evidence
Campaign scan 2 hosted-environment findings HE-51/52/53/56: 3 owned official hosted Sessions, all deleted. Recorded in
contracts/agents-api/official-semantics-alignment.mdandoperation-evidence.mdrows 16–19.Validation
Live acceptance through Core, the daemon, native Codex and the real Kimi K3 model, on the Core-managed Docker
openai_hostedRuntime. The run was built from the exact source, including the Rust export helper, whose hash was checked inside the running Runtime containers.artifact_capture_failed)a.txt, writing onlyc.txtc.txtanda.txtpublishedsub/b.txtsub/b.txt, new bytes (the old Artifact keeps the old bytes)3 Turns per phase. Cleanup and secret scans passed.
Rust export tests: every link kind, plus an inotify proof (with a positive control) that no link target is opened; rustfmt and clippy clean.
Real-PostgreSQL store tests: new, unchanged, changed, changed-back and deleted-then-unchanged paths; producing-Turn ordering; a truly concurrent deletion; no stored-byte leaks.
HTTP tests: envelope and filter, with tenant isolation.
Server gate on this head:
make -o check-web checkplus Web typecheck, core-doctor, 301 client tests, 602 Web tests and the build all pass. The Playwright browser cases were not run on the server (no Google Chrome; skip approved by the user).Generated files:
make sqlc-generateandmake openapiare byte-identical.Independent blind review by a fresh Claude Code subagent (the user-approved replacement for GPT-6 Astra): no blockers. Its follow-ups (two regression tests, doc qualification, the documented A2 edge) are in the last two commits, which touch only tests and docs.
Deferred
outputsroot still fails the Turn.aftercursor status (HE-57).No full protocol compatibility is claimed.
Need help on this PR? Tag
@codesmithwith what you need. Autofix is disabled.