Skip to content

F2b: connect the real lane D workspace (#87, part 1) - #92

Merged
mchwang merged 4 commits into
mainfrom
feat/f2b-real-workspace
Oct 1, 2026
Merged

mchwang merged 4 commits into
mainfrom
feat/f2b-real-workspace

Conversation

@mchwang

@mchwang mchwang commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Part 1 of #87. It connects F2b's per-item execution to the real lane D workspace from #90, following the decisions on #87. Part 2 (items 1–4: partial export, failed-run audit, durable findings) follows. Production wiring is #91.

What it adds

Piece What it does
runner/runner-repository.ts The runner-owned bare repository: <runnerRoot>/<owner>/repositories/<hash>.git, in owner-only directories.
• ensureCommit fetches a base commit from the user's repository by ID, taking no refs, tags or FETCH_HEAD.
• fetchTaskCommit takes a runner commit in only if the bundle's one ref is the commit's ID and the commit is a single commit on its base. It is kept under refs/codeboost/attempts/<id>; a failed check removes that ref.
• dropAttemptRef.
runner/workspace.ts The real TaskWorkspace:
• materialize makes the head available, clones the runner repo into <runnerRoot>/<owner>/attempts/<id>, and allocates task storage. It records the preparation start, every process group and the allocation ID, as startup recovery expects.
• snapshotDeclaredLinks, inspectChanges and commit call D.
• commit checks the bundle and takes the commit into the runner repo.
• release removes storage, then drops the commit of an attempt that did not complete.
• cleanupPreparation removes the staging clone.
runner/execution.ts TaskWorkspace.commit takes the link snapshot instead of paths, since D commits exactly the approved manifest. Adds optional cleanupPreparation. snapshotDeclaredLinks is typed.
core/run-audit.ts Reads D's manifest shape:
• ignored: true is a safety violation (decision 2).
• linkTargetChanges are D's objects. A retargeted entry passes only when the manifest lists the edit of that link; every other kind is a violation.
• Malformed entries fail closed.
D (tree-script.ts, changes.ts) The inspection reports linkTargetTraversesLink for every changed symlink (decision 3): whether its new target, resolved one part at a time as the kernel would, passes through another link. A target outside the work tree or in .git counts as true.
runner/review.ts, runner/questions.ts Once a task has a completed execute or fix attempt that made a commit, the review and Ask read it from the runner-owned repository at the head the Store recorded (item 5). The user's HEAD is no longer observed for that task, since that would roll it back. Every other task still observes HEAD. Without the runner repository configured, such a task's review fails instead of reading the user's repository.

Change from the posted decision 1

Decision 1 said the review reads "a per-task ref". It now reads the head the Store recorded, and each attempt keeps its own ref. A task ref updated around the terminal write could disagree with the Store after a crash; the Store's head cannot.

I first keyed "runner-owned" on owned ledger entries. That broke the demo, scripts/plant.ts and several browser tests, which record owned entries on a branch in the user's repository. So the signal is a completed writable attempt that made a commit.

Validation (local; first pushed head f6da9ef, later heads in the rounds below)

  • npm run typecheck: passes.
  • Regular test job: 1163 of 1163 pass. An earlier run under a load average of about 150 had timeout failures, which passed on rerun.
  • New real-Docker test/runner-workspace.test.ts (added to real-docker and excluded from test): 2 of 2 pass. It runs ItemExecutor with the real workspace and a stand-in agent container:
    • Two items chain runner commits in the runner repository. The exact author, date, message and trailers are checked. The user's refs are untouched, and storage and staging directories are removed. The review reads the recorded head with P1's lines, and refuses without the runner repository.
    • A base-ignored file sends the task to needs human, with no ref kept.
    • The storage check was verified to fail when removal is skipped.
    • One earlier run failed without its error captured; three later full runs passed.
  • New real-Docker test: linkTargetTraversesLink in eight cases.
  • test/runner-repository.test.ts: 4 host-Git tests.

Review rounds (/code-review high)

Round Fixed Declined
1 • Each preparation group is now recorded with its own start time. Before, recovery's start-time check missed every group after the first.
• A spawn that fails at once clears the preparation marker.
• Release removes storage without blocking the event loop (new D removeTaskFilesystemsAsync).
• The traversal check no longer refuses the whole inspection on a long joined path.
• The review asks the Store hasRunnerCommit instead of decoding every attempt.
• Runner directories are owner-only.
• real-docker path triggers.
None
2 • The traversal check reports a link on the way by status alone, since its anchor entry still checked names.
• Ownership is checked at every level from the runner root down.
None
3 Docs: the linkTargetTraversesLink field, asynchronous removal, and ignored files and retargeted in publishing step 2. None
4 Nothing new. None

Each new test was checked to fail with its fix removed: the preparation marker, the long path, the anchor names, and storage removal.

Review lessons (merge gate)

  • Preparation start time: a one-off contract mismatch between recordPreparationGroup and recovery's PID-reuse check. It is now covered by the API itself.
  • Event-loop blocking removal: covered by AGENTS.md "Owned host and Docker resources", and by the existing practice of async variants for server paths.
  • Name checks on unreported paths: covered by "Check a name's safety only where it is reported".
  • Ownership of every directory: covered by "Keep files that other local users must not plant or swap … in a directory only the current user can write".
  • Doc drift: covered by "Review readiness" (behavioural claims in docs).

🤖 Generated with Claude Code

mchwang and others added 4 commits October 1, 2026 11:12
The real TaskWorkspace (runner/workspace.ts) clones the runner-owned
repository at the recorded head into bounded task storage, runs D's link
snapshot, inspection and commit, and takes the commit's bundle into the
runner-owned repository (runner/runner-repository.ts) under the attempt's
ref. Release removes storage and drops an unpublished commit. The user's
repository is only read, for base commits.

The audit reads D's manifest shape: base-ignored files are safety
violations, and a retargeted declared link is allowed only with its own
edit. D reports linkTargetTraversesLink for changed symlinks. The review
screen and Ask read a task the runner has committed to from the
runner-owned repository at the recorded head, instead of observing HEAD.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Each preparation group is recorded with its own start time, so startup
  recovery's start-time check matches the group it finds.
- A preparation whose first spawn failed at once clears its marker.
- Release removes task storage without blocking the event loop
  (removeTaskFilesystemsAsync).
- The traversal check resolves a changed link's target without checking
  the joined path as a name, so a long one no longer refuses the inspection.
- The review asks the Store whether the runner committed (hasRunnerCommit)
  instead of decoding every attempt.
- Every runner directory is checked owner-only, its parent included.
- real-docker also runs for review, store and history changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The traversal check reports a link on the way by status alone, so an
  unchanged link whose target text no manifest could carry no longer
  refuses the inspection.
- Runner directories are checked owner-only at every level from the
  runner root down.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…les (review round 3)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant