Repository navigation
feat: worktrees codeoid creates can build — a trusted, hash-pinned setup recipe - #424
Merged
Merged
Conversation
rsharath
approved these changes
Oct 25, 2026
A worktree codeoid creates (forks, compare branches, routine worktrees) now applies the repository's .codeoid/setup.yaml: copy ignored files from the primary checkout, symlink shared directories, and run a setup command. - The recipe is read from the primary checkout's committed HEAD (git ls-tree + cat-file), never from the new worktree, a candidate branch, or uncommitted edits. Reading and parsing execute nothing. - copy/share resolve inside the primary checkout only (realpath containment, never .git); destinations never follow a symlink and never overwrite; a copied file must be git-ignored where it lands. - The command runs only under worktree.setup.runCommands "trusted-repos" for a repository in worktree.setup.trustedRepos (default never), with a built environment, a timeout that kills its process group, and an abort. - Everything is validated before anything is written; an apply failure rolls back. resources are parsed and exposed, not yet leased. - Forks prepare in the background ahead of fork.setup; the first turn waits and the result is an infrastructure notice. Routine worktrees prepare before the agent starts; a failure fails the run as an infrastructure error and removes only that run's fresh worktree. - What a recipe added is recorded on SessionWorktree.setup, kept out of checkpoints (excludePaths), and taken back out before the destroy-time WIP snapshot so a copied secret or shared link never lands in a commit. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ound 1)
- A setup command runs on a CLEAN checkout of the worktree's base commit:
createForkWorktree({ deferCarry }) hands the parent's uncommitted state
back as `carry`, applied with applyCarriedState after setup (forks: with
the earlier-turn file roll, in the first-turn gate; routines: before the
agent starts). Trust is documented as covering the committed tree,
including the scripts the command invokes.
- Trust is pinned to the recipe's git blob, direnv-allow style:
worktree.setup.trustedRepos takes { path, recipeHash } (a bare path
trusts no command). `codeoid worktree trust [dir] [--off] [--yes]` and a
worktree.trust message show the command and record path + hash (refused
if the recipe changed since shown). A changed recipe runs no command,
still copies/shares, and surfaces worktree.setup.untrusted.
- worktree.setup.* settings and worktree.trust refuse delegated tokens and
are audited old -> new; the policy is applied live.
- The command's process group is killed on exit too, settles on exit and
drains briefly; routine runs pass their abort signal (cancel, shutdown).
- share refuses .claude, .codeoid, .vscode, .idea, .husky and .git*.
- Copies skip files with other hard links and go through O_NOFOLLOW fds
re-checked by fstat; .git is matched case-insensitively with native
realpaths; trust paths must be absolute; off on win32.
- Output tails are redacted; checkpoint exclusion fails closed; setup is
recorded before the destroyed check; a failed routine setup cleans up
with its setup paths; share planning can't throw.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…y history (B0d audit round 2) - commandGate: besides the pinned trust, the worktree's actual base commit must carry the trusted recipe blob and be an ancestor of the primary checkout's HEAD; otherwise the command is skipped as base-not-on-primary (worktree.setup.untrusted notice), copy/share still apply. Forks decide whether to hold their files back from the same check, on the created worktree, with one policy snapshot passed through to prepare. - A failed carry rolls the worktree back to HEAD (tracked files restored, carried additions removed) and says so. - While a fork's files wait for setup, fs.list/fs.read, forking it and rewinding it are refused with a "still preparing" error. - After a carry, shared links git replaced are reported. - worktree.trust builds trustedRepos from config.json as it is now (hand edits survive), records trustedBy, and an entry holds only for that owner's sessions; refusals are audited as *.denied. - Docs: the base-commit rule, and that an agent in the primary checkout can still commit to its branch. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
saucam
force-pushed
the
feat/loops-workspace-setup
branch
from
October 25, 2026 02:18
bad51d4 to
50e4d28
Compare
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.
What this adds
A worktree codeoid creates (forks, compare, routine worktrees) can now actually build and run tests. Until now it carried tracked files only, so
.envand installed dependencies were missing and checks failed for reasons unrelated to the change being tested (docs/loop-engineering-design.md §12.7).A repo can declare
.codeoid/setup.yaml:copy: gitignored files to copy from the primary checkout, for example.env.share: directories to link from the primary checkout, for examplenode_modules. A share is a writable link back into the primary checkout, and config directories such as.claude,.vscodeand.git*are refused.command: an optional setup command such asbun install --ignore-scripts.Running the command is something you opt into, and it is pinned:
worktree.setup.runCommands: trusted-repos, and only for a recipe you trusted withcodeoid worktree trust. Trust is recorded against the recipe's content hash and for your user.What setup copied or linked is kept out of checkpoints and out of the branch a destroyed fork leaves behind. A failed setup is reported as an infrastructure problem, not the agent's.
Review
Two independent security and correctness audit rounds, all findings folded in.
Full suite: 3803 pass / 0 fail.
For agents: implementation
src/daemon/workspace-setup.ts:ls-tree/cat-file, through a zod schema.trustDecisionchecks the base blob hash andmerge-base --is-ancestor.git-worktree.ts:createForkWorktree({ deferCarry })withapplyCarriedState(restored to HEAD on failure).session-manager.ts:prepareWorktree;worktree.trusthandler refuses delegated tokens and audits refusals;session.ts: setup notices, plus#pendingSetuprefusing fs, fork and rewind while preparing.checkpoints.ts: fail-closed exclusion of setup paths.worktree.setup.runCommands;trustedReposentries are{path, recipeHash, trustedBy}.worktree-trust.ts. CLI:codeoid worktree trust [dir] [--off] [--yes]. Docs: CONFIGURATION.md.🤖 Generated with Claude Code