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
11 changes: 7 additions & 4 deletions .agents/skills/worktrees-pnpm/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ problem in this repo.

```bash
git fetch origin +refs/heads/main:refs/remotes/origin/main # explicit refspec; see the hook for why
git worktree add -b <branch> .agents/.worktrees/<name> origin/main # or an existing <branch>, without -b
git worktree add --no-track -b <branch> .agents/.worktrees/<name> origin/main # or an existing <branch>, without -b
cd .agents/.worktrees/<name>
git rev-list --count HEAD..origin/main # anything above 0: git rebase origin/main
pnpm install # ← not optional, and after the rebase, which can change the lockfile
Expand Down Expand Up @@ -149,8 +149,10 @@ main checkout are not enough on their own, so a `pre-commit` hook makes it a har
`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
refused too. It skips when `GITHUB_ACTIONS=true`, because a runner commits from a plain clone
(version PRs, review bots), and a plain clone is a primary checkout. It keys on the variable the
runner sets itself rather than `CI`, which any shell can export as an unnamed bypass. A repo on
another CI system adds that system's own marker. With Husky, run it first in
`.husky/pre-commit`:

```sh
Expand All @@ -160,7 +162,8 @@ 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.
An agent never gets past it, whether by passing `--no-verify`, setting `GITHUB_ACTIONS`, or any
other means; a refusal means it is in the wrong checkout, so it moves to a worktree.

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

Expand Down
16 changes: 11 additions & 5 deletions .agents/skills/worktrees-pnpm/scripts/refuse-main-checkout.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@
# 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.
# Not enforced on GitHub Actions: the runners commit from a plain clone
# (changeset version PRs, review actions), and a plain clone is a primary
# checkout. The skip keys on GITHUB_ACTIONS, which the runner sets itself,
# rather than CI, which any shell exports and which no instruction names, so it
# would be a quieter bypass than --no-verify.
#
# Escape hatch for a deliberate commit here: git commit --no-verify
# Escape hatch for a person's deliberate commit here: git commit --no-verify
set -eu

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

Expand All @@ -37,9 +40,12 @@ 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
git worktree add --no-track -b <branch> .agents/.worktrees/<name> origin/main
cd .agents/.worktrees/<name> && pnpm install

--no-track keeps the branch from tracking origin/main, so a bare git push
cannot target main.

To commit here deliberately: git commit --no-verify
EOF
exit 1
Expand Down
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,17 +106,17 @@ This is where taskless/cli differs from the other Taskless repos, which run the

```bash
git fetch origin +refs/heads/main:refs/remotes/origin/main
git worktree add -b <branch> .agents/.worktrees/<name> origin/main
git worktree add --no-track -b <branch> .agents/.worktrees/<name> origin/main
cd .agents/.worktrees/<name> && pnpm install
```

The refspec is spelled out because a bare `git fetch origin main` only updates `origin/main` when `remote.origin.fetch` covers it, which a single-branch clone of another branch does not. Branch from `origin/main`, never the local `main`: the local branch is whatever the main checkout was last pulled to.
The refspec is spelled out because a bare `git fetch origin main` only updates `origin/main` when `remote.origin.fetch` covers it, which a single-branch clone of another branch does not. Branch from `origin/main`, never the local `main`: the local branch is whatever the main checkout was last pulled to. `--no-track` keeps the new branch from tracking `origin/main`, so a bare `git push` cannot target `main`.

The three rules that cause the most damage when missed:

- **A worktree starts from the latest `origin/main`, checked before any work begins.** Confirm `git rev-parse --is-shallow-repository` prints `false` (a shallow clone cannot answer the next question; see the shallow-clone entry above), 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 it, `.husky/_` does not exist in the worktree (the install generates it; it is not tracked), and since `core.hooksPath` points there, `git commit` runs no hooks at all: `lint-staged` and the primary-checkout guard are both skipped, silently. Every `pnpm` script fails too. 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`). 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.
- **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 (except on GitHub Actions runners), whatever branch is checked out there. An agent never gets past it, whether by passing `--no-verify`, setting `GITHUB_ACTIONS`, or any other means; a refusal means you are in the wrong checkout, so move to a worktree. `--no-verify` is for a person's deliberate commit only.

**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.

Expand Down
Loading