Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .agents/skills/worktrees-pnpm/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,24 @@ printf '{"hook_event_name":"WorktreeRemove","worktree_path":"%s"}' "$PWD/.agents
**2. Add the ignores** described in the next section. Skipping this is the single most common way
to end up debugging a lint failure in code you never wrote.

**3. Refuse commits in the primary checkout.** Instructions telling agents to stay out of the
main checkout are not enough on their own, so a `pre-commit` hook makes it a hard stop.
`scripts/refuse-main-checkout.sh` exits non-zero when `git rev-parse --git-dir` equals
`--git-common-dir`, which is true only in the primary checkout, never in a linked worktree. It
checks the checkout, not the branch, so a feature branch checked out in the primary tree is
refused too. It skips when `CI` is set, because a CI runner commits from a plain clone (version
PRs, review bots), and a plain clone is a primary checkout. With Husky, run it first in
`.husky/pre-commit`:

```sh
sh .agents/skills/worktrees-pnpm/scripts/refuse-main-checkout.sh
Comment thread
theCodeDrift marked this conversation as resolved.
Resolved
npx lint-staged
```

Without Husky, call it from `.git/hooks/pre-commit`, or from whatever hook manager the repo
uses. A person who really means to commit in the primary checkout runs `git commit --no-verify`.
An agent never does that to get past this guard.

## Every root-level tool must ignore the worktrees directory

This is the one real cost of keeping worktrees in the repo, and it is not optional. A worktree is
Expand Down
42 changes: 42 additions & 0 deletions .agents/skills/worktrees-pnpm/scripts/refuse-main-checkout.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/usr/bin/env sh
#
# pre-commit guard: refuse commits made in the repository's primary checkout.
#
# All work happens in a worktree under .agents/.worktrees/. The primary checkout
# is the one every agent is told never to touch, because an agent that lands
# there edits, branches and commits in the user's own working tree. Telling it
# not to has not been enough, so the commit is refused instead.
#
# The test: in a linked worktree, --git-dir is .git/worktrees/<name> while
# --git-common-dir is the shared .git. In the primary checkout both are the same
# directory. This does not care what branch is checked out, so it also stops a
# commit to a feature branch that someone checked out in the primary tree.
#
# Not enforced in CI: the runners commit from a plain clone (changeset version
# PRs, review actions), and a plain clone is a primary checkout.
#
# Escape hatch for a deliberate commit here: git commit --no-verify
set -eu

if [ -n "${CI:-}" ]; then
exit 0
fi

dirs=$(git rev-parse --path-format=absolute --git-dir --git-common-dir)
git_dir=$(printf '%s\n' "$dirs" | sed -n 1p)
common_dir=$(printf '%s\n' "$dirs" | sed -n 2p)

if [ "$git_dir" = "$common_dir" ]; then
top=$(git rev-parse --show-toplevel)
cat >&2 <<EOF
Refusing to commit in the primary checkout ($top).

Work in a worktree instead:
git fetch origin +refs/heads/main:refs/remotes/origin/main
git worktree add -b <branch> .agents/.worktrees/<name> origin/main
cd .agents/.worktrees/<name> && pnpm install

To commit here deliberately: git commit --no-verify
EOF
exit 1
fi
1 change: 1 addition & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
sh .agents/skills/worktrees-pnpm/scripts/refuse-main-checkout.sh
Comment thread
theCodeDrift marked this conversation as resolved.
Resolved
npx lint-staged
4 changes: 3 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,9 @@ The three rules that cause the most damage when missed:

- **A worktree starts from the latest `origin/main`, checked before any work begins.** Run `git fetch origin +refs/heads/main:refs/remotes/origin/main` (the explicit refspec, for the reason above), then `git rev-list --count HEAD..origin/main`. Anything above `0` means the worktree is behind: rebase onto `origin/main` (`git rebase origin/main`) before reading code, making changes, or running `pnpm install` (a rebase can change the lockfile), unless the work was explicitly asked to start from another branch or commit. The `WorktreeCreate` hook already branches from a freshly fetched `origin/main`, but a worktree made any other way (by hand, by another tool, or by a session board) is cut from whatever the main checkout was last pulled to. One started 68 commits behind `main`, and nothing flagged it: stale code reads exactly like current code. Another started 156 behind and never saw this rule at all, because the rule landed after its base: **a stale tree carries stale conventions**, so nothing in the repo can warn a checkout that is already behind. That is why the fix belongs in whatever creates the worktree (the hook, a session board, your own hands), and why this check comes before reading anything else here.
- **A worktree gets its own empty `node_modules`.** `git worktree add` is not finished until `pnpm install` has run inside it. Without that, `git commit` fails in `lint-staged` (no `prettier`/`eslint`), and every `pnpm` script fails. A missing `prettier` here once cost an agent an hour of dead-end workarounds. There is no `pnpm worktree` command; `git worktree` is the tool.
- **NEVER point an agent at the main repo path** (e.g. `/Users/<you>/code/taskless/skills`). It will `cd` there and run git commands and edits in the **main** checkout, defeating isolation. It can create and check out a branch in your working tree, silently switching your session off its own branch. Tell the agent to work in **its assigned worktree** (`$PWD`) and pass only relative paths plus GitHub identifiers (`owner/repo`).
- **NEVER point an agent at the main repo path** (e.g. `/Users/<you>/code/taskless/skills`). It will `cd` there and run git commands and edits in the **main** checkout, defeating isolation. It can create and check out a branch in your working tree, silently switching your session off its own branch. Tell the agent to work in **its assigned worktree** (`$PWD`) and pass only relative paths plus GitHub identifiers (`owner/repo`). The `pre-commit` hook enforces the commit half of this: it refuses any commit made in the primary checkout (outside CI), whatever branch is checked out there. `git commit --no-verify` is the deliberate override; an agent never uses it to get past this guard.

**Do not offer to fast-forward or pull the local `main`.** Nothing here reads it: worktrees branch from a freshly fetched `origin/main`, and every freshness check compares against `origin/main`. So "want me to fast-forward `main`?" at the end of a task asks for a step that changes nothing. The one time it is worth mentioning is right after this session's own work has been rebased and merged to `main`, and even then mention it once, as optional, not as a closing step.

## Stacked PRs

Expand Down
Loading