Repository navigation
D: inspect task changes and make runner commits (needed by F2) #66
Description
Lane D follow-up needed by F2 (per-item execution). Related: #51 (other D prerequisites), #22.
Why
The design says codeboost makes every commit. After an execute or fix invocation, the runner must inspect what the agent changed, audit it against the plan item's declared files, undo any commits the agent made, and commit the changes itself with Plan-Item/Plan-Revision trailers (docs/plan-format.md, "After each run"; design, "How codeboost runs a plan").
The work lives in D's task volumes. /work/.git is read-only to the agent, and F must never write Git metadata from the host. D exposes no operation to read changes back or to commit, so F2's commit step has no real target.
Proposed operations (names are suggestions)
All take a TaskFilesystems value or a recovered storage handle (#51), run in a runner-controlled container (not the agent's), take an AbortSignal with an overall deadline, settle only after their own container stops, and bound their output.
-
snapshotDeclaredLinks(storage, paths), before launch. A no-follow snapshot of every declared symlink target (identity, type, mode, content hash, descendants for directory targets, and absent-path identity for dangling links). It is returned as data and kept by F. -
inspectTaskChanges(storage, { baseHead, linkSnapshot }), after settlement. Returns a change manifest, read without following links:changes[]:{ path, oldPath?, kind: 'add'|'modify'|'delete'|'rename'|'mode', oldType?, newType?, oldMode?, newMode?, newLinkTarget?, underGit: boolean }, where types arefile|symlink|gitlink|directory|other;agentCommits[]: commits the agent made on top ofbaseHead;metadataChanged: whether anything under.gitchanged (config, hooks, refs, objects);linkTargetChanges[]: every difference fromlinkSnapshot;nestedGitlinkContent[]: any content under gitlink paths.
A bound on the number of changes must fail closed, never truncate.
-
commitTaskChanges(storage, { baseHead, message, trailers, author }). Undoes agent commits while keeping their changes, stages exactly the audited paths, and commits with hooks disabled (core.hooksPath=/dev/null) and isolated Git config. Returns the new head SHA. It must refuse when the work tree differs from the manifest the audit approved (pass the manifest digest). -
materializeTaskFilesystems(clone, head, limits). Covered by the "clean invocation state" rule: a fresh filesystem from the recorded trusted head before every invocation, never a reset of a used one. This may simply beprepareTaskFilesystemsfrom a clone athead; confirm and document it.
F2a will implement the audit as a pure function over exactly this manifest shape, so the shapes should be agreed here first.
Done when
- The operations are exported with contract tests. The real-Docker suite covers:
- a new symlink, and writes through a link to an undeclared file, a directory child and a dangling target;
- a
.gitchange; - an agent commit, which is undone while its changes are kept;
- a gitlink with nested content;
- an oversized change set failing closed;
commitTaskChangesrefusing a work tree that changed after the audit.
- Each operation is abortable, has a deadline, and settles only after its container stops.
Activity
mchwang commentedon Sep 29, 2026
Decisions before implementation (2026-09-28)
Research on
mainfound two gaps in this issue as written:/work/.gitin any phase. The metadata volume is mounted read-only (agents/container/profile.ts), and the validator refuses a container without that mount (agents/container/run.ts). So agent commits and.gitchanges cannot happen today.The decisions:
exportTaskCommit(storage, { base, head })to this issue. A read-only container writes a boundedgit bundleto stdout. F fetches it into a runner-owned host repo and checks that the SHA matches the audited commit. "F must never write Git metadata from the host" becomes "only through a verified bundle"..gitchangesagentCommitsandmetadataChangedin the manifest as fail-closed checks, compared against a metadata digest taken at seeding. A non-empty result means needs human, andcommitTaskChangesrefuses. No undo path is built for a case the mounts prevent.commitTaskChangesrequires author and committer (name, email, date) from F. D takes nothing from the environment, so the SHA is reproducible from recorded inputs.Settled with those, unless there are objections:
commitTaskChangesrecomputes the manifest in its own container and compares its digest before staging, so the check and the commit cannot drift apart.baseHeadwith no commit ("planned but unchanged").baseis the clone's head.D: inspect what an agent changed in task storage (#66, part 1) (#85)
F2a: execute/fix prompts, hostile-issue eval set, and the post-run audit
F2b: per-item execution and the runner's commit step
F2a: execute/fix prompts, hostile-issue eval set, and the post-run audit
F2b: per-item execution and the runner's commit step
F2a: execute/fix prompts, hostile-issue eval set, and the post-run audit
F2b: per-item execution and the runner's commit step
F2a: execute/fix prompts, hostile-issue eval set, and the post-run audit
F2b: per-item execution and the runner's commit step
F2a: execute/fix prompts, hostile-issue eval set, and the post-run audit
F2b: per-item execution and the runner's commit step
F2a: execute/fix prompts, hostile-issue eval set, and the post-run audit
F2b: per-item execution and the runner's commit step
F2a: execute/fix prompts, hostile-issue eval set, and the post-run audit
F2b: per-item execution and the runner's commit step
F2a: execute/fix prompts, hostile-issue eval set, and the post-run audit
F2a: execute/fix prompts, hostile-issue eval set, and the post-run au…
F2b: per-item execution and the runner's commit step
Fix the independent review of F2b: runner-owned findings, storage on …
F2b: per-item execution and the runner's commit step (#68)
mchwang commentedon Oct 1, 2026
Decision 1 revised (2026-10-01): Part 2 is in #90.
There is no separate
exportTaskCommit.commitTaskChangesdoes both steps:/tmp.git bundle: one ref,refs/heads/codeboost, withbaseas its prerequisite.Node keeps the bundle only when:
The metadata volume is never writable after seeding. Calling it again with the same inputs gives the same SHA.
Why: the manifest digest is computed in TypeScript (
linkTargetChangescomes from the snapshot). So a container could not check it before writing to the metadata volume. Here, nothing is kept until it is checked.Two contract points, from review:
retargetedlink-target entry (the item editing a declared link) is not refused. The audit judges it.🤖 Generated with Claude Code
D: make the runner's commit from audited task changes (#66, part 2) (#90
mchwang commentedon Oct 20, 2026
Done: part 1 in #85 (task-change inspection) and part 2 in #90 (runner commit as a bounded git bundle), both merged. Recorded in docs/architecture.md, lane D row.