docs(agents): tea CLI and per-branch worktree workflow #139

Merged
gabogg merged 2 commits from docs/agent-workspaces into master 2026-09-26 16:28:37 +00:00
Owner

Problem

  • Agents drove Forgejo through hand-built curl calls with the token read from disk: verbose, error-prone, and every session re-derived the endpoints.
  • Concurrent agent sessions shared the main checkout (or ad-hoc worktrees in session scratchpads), switching branches and leaving uncommitted work under each other.

Approach

  • Forgejo CLI: adopt tea (Gitea CLI; Forgejo speaks the same API). docs/agents/forgejo-cli.md covers install with checksum, login plus git credential helper, the everyday command table, merging via tea api (keeps delete_branch_after_merge), and the credential-free remote URL gotcha. issue-tracker.md now points at tea instead of listing REST endpoints.
  • One worktree per branch: scripts/wt manages .worktrees/<type>-<slug>/:
    • new <type>/<slug> [base] branches from fresh origin/master (or a stacked base) and enforces AGENTS.md branch naming.
    • pr <N> opens a PR's branch, reusing a clean existing worktree and refusing a dirty one (owned by another session).
    • ls / rm / prune list, remove (refusing dirty or unpushed work), and clean up merged branches whose remote branch is gone.
    • New worktrees symlink .venv, .env and scripts/tailwindcss from the main checkout, and deliberately not data/, so sessions never share a SQLite file.
  • Rules: docs/agents/workspaces.md covers the main checkout staying on a clean master, one owner per worktree, commit-and-push as the handover point, WIP commits instead of the shared stash, and cleanup. AGENTS.md §4 inlines the core rules and points to both docs.

Verification

  • Smoke-tested each wt command: new (including stacked base and bad-name rejection), pr (dirty refusal, clean reuse), ls, rm (dirty refusal, merged-branch deletion), and prune.
  • Full suite passes inside a fresh worktree (397 passed); pre-commit hooks (ruff, pytest) pass; scripts/check_docs.py is clean.
  • tea whoami, tea pr list, tea issues list and tea api work against the instance, and a git push --dry-run authenticated through the tea credential helper.

🤖 Generated with Claude Code

## Problem - Agents drove Forgejo through hand-built `curl` calls with the token read from disk: verbose, error-prone, and every session re-derived the endpoints. - Concurrent agent sessions shared the main checkout (or ad-hoc worktrees in session scratchpads), switching branches and leaving uncommitted work under each other. ## Approach - **Forgejo CLI:** adopt [`tea`](https://gitea.com/gitea/tea) (Gitea CLI; Forgejo speaks the same API). `docs/agents/forgejo-cli.md` covers install with checksum, login plus git credential helper, the everyday command table, merging via `tea api` (keeps `delete_branch_after_merge`), and the credential-free remote URL gotcha. `issue-tracker.md` now points at `tea` instead of listing REST endpoints. - **One worktree per branch:** `scripts/wt` manages `.worktrees/<type>-<slug>/`: - `new <type>/<slug> [base]` branches from fresh `origin/master` (or a stacked base) and enforces AGENTS.md branch naming. - `pr <N>` opens a PR's branch, reusing a clean existing worktree and refusing a dirty one (owned by another session). - `ls` / `rm` / `prune` list, remove (refusing dirty or unpushed work), and clean up merged branches whose remote branch is gone. - New worktrees symlink `.venv`, `.env` and `scripts/tailwindcss` from the main checkout, and deliberately not `data/`, so sessions never share a SQLite file. - **Rules:** `docs/agents/workspaces.md` covers the main checkout staying on a clean `master`, one owner per worktree, commit-and-push as the handover point, WIP commits instead of the shared stash, and cleanup. AGENTS.md §4 inlines the core rules and points to both docs. ## Verification - Smoke-tested each `wt` command: `new` (including stacked base and bad-name rejection), `pr` (dirty refusal, clean reuse), `ls`, `rm` (dirty refusal, merged-branch deletion), and `prune`. - Full suite passes inside a fresh worktree (397 passed); pre-commit hooks (ruff, pytest) pass; `scripts/check_docs.py` is clean. - `tea whoami`, `tea pr list`, `tea issues list` and `tea api` work against the instance, and a `git push --dry-run` authenticated through the tea credential helper. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
docs(agents): tea CLI and per-branch worktree workflow
All checks were successful
CI / lint-and-test (pull_request) Successful in 2m0s
537247334f
Agents were hand-building curl calls against the Forgejo API and sharing
one working tree across concurrent sessions. Adopt the tea CLI for all
Forgejo operations and give every branch its own worktree under
.worktrees/, managed by scripts/wt (new, pr, ls, rm, prune), with
ownership and handover rules in docs/agents/workspaces.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Author
Owner

Code review: PR #139 (docs/agent-workspaces vs master @ 8f7a80e)

Two independent axes. Standards: AGENTS.md, docs/standards/code-standards.md, docs/standards/git-and-workflow.md, .claude/skills/writing-for-agents, plus the Fowler smell baseline. Spec: the maintainer's request (Forgejo CLI with usage instructions; a clean, separated, standardized workspace per PR; agents able to work on each other's branches cleanly; an open, non-draft PR).

Standards

P1 (hard, data loss)

  • S1. scripts/wt remove() / is_dirty(). The dirty check uses git status --porcelain, which skips ignored files, and git worktree remove then deletes them. Reproduced: a plain wt rm (no -f) deleted data/app.db. workspaces.md tells each worktree to create its own DB under the ignored data/, so rm and prune wipe exactly what the doc tells agents to create. Fix: treat ignored files outside the known symlinks as dirty (or refuse when data//logs/ has content).

P2

  • S2. docs/standards/git-and-workflow.md was not updated. It still allows chore/<topic>, which AGENTS.md and wt (TYPES="feat fix refactor docs") reject. Its §2.1 Draft PRs and §2.3 Branch Cleanup repeat the new AGENTS.md lines and forgejo-cli.md, which breaks single source of truth (writing-for-agents §Pruning).
  • S3. cmd_pr handover. It checks only for uncommitted changes. A clean worktree with unpushed commits passes merge --ff-only and is handed over, contradicting "Committed-and-pushed work is the handover point". Fix: also require unpushed == 0.
  • S4. workspaces.md repeats the branch-name rule ("follows AGENTS.md (feat|fix|refactor|docs/<slug>); the script refuses anything else"). This caches AGENTS.md and the script's own error message. Replace it with a pointer.

P3

  • S5. Negations where positive targets fit (writing-for-agents §Negation):
    • AGENTS.md: "never switch its branch or edit in it", "leave it untouched", "not raw curl".
    • forgejo-cli.md: "no reason to hand-build curl…", "never inline in the shell".
    • workspaces.md: "never force-push". Keep this one as a guardrail, but pair it with the positive target.
  • S6. AGENTS.md sub-bullets repeat workspaces.md (ownership, "end every session committed and pushed"). That material is always loaded but already sits behind a pointer.
  • S7. cmd_new fetches before validating its arguments. A bad invocation still hits the network.
  • S8. remove() treats any non-empty second argument as --force, while its safety checks key only on -f. Reject unknown flags.
  • S9. bootstrap() writes /.worktrees to info/exclude, repeating the new .gitignore line (possible Duplicated Code).
  • S10. local upstream sits inside the prune loop. Declare it with local p branch.

Held up: quoting; no local x=$(...) masking; prune needs a gone upstream and ancestry of master, so unpushed and squash-merged branches are kept; rm is limited to .worktrees/; odd branch names are safe.

Spec

The documented tea commands exist in tea 0.16.0, except the one in C4. The merge body fields (Do, MergeTitleField, delete_branch_after_merge) match MergePullRequestOption. The PR is open, not WIP.

(a) Missing or partial

  • C1. P2, "separated and standarized". git-and-workflow.md disagrees with the new policy (chore/*, no mention of worktrees or tea). Same root as S2.
  • C2. P2, "work on other's stuff if needed in a clean and neat way". The same-branch case has only a dirty check. An owner who committed but hasn't pushed, or is between edits, gets a second session in the same tree and HEAD. Same root as S3. A lease marker would make this robust.

(b) Scope creep

  • C3. None. The symlinks, info/exclude and the issue-tracker.md rewrite all serve the request.

(c) Implemented but wrong ("provide instrucctions of use")

  • C4. P3. tea pr list --labels X: that flag doesn't exist for pulls list; only issues list has it.
  • C5. P2. "Read an issue / PR with comments: tea issues 42 · tea pr 42". Comments appear only with --comments, and agents run non-interactively, so they miss review comments.
  • C6. P3. "Promote draft → ready": tea pr edit 42 --ready strips WIP: itself, which is less error-prone than retyping the title.
  • C7. P3. The documented merge title <PR title> (#N) matches stated policy, but recent history uses Forgejo's default title. The maintainer should confirm which is intended.
  • C8. P3. No repo CLAUDE.md. Codex reads AGENTS.md natively; Claude relies on the harness. A one-line CLAUDE.md with @AGENTS.md makes discovery reliable.
  • C9. P3. workspaces.md "rm -f" reads as wt rm -f <x>, but only wt rm <x> -f works. The wrong order fails safely.

Summary. Standards: 10 findings, worst S1 (wt rm/prune silently delete ignored files such as the worktree's own data/ DB). Spec: 8 findings, worst C2 (the same-branch handover isn't isolated when the owner has unpushed commits).

🤖 Generated with Claude Code

# Code review: PR #139 (`docs/agent-workspaces` vs `master` @ 8f7a80e) Two independent axes. **Standards**: AGENTS.md, `docs/standards/code-standards.md`, `docs/standards/git-and-workflow.md`, `.claude/skills/writing-for-agents`, plus the Fowler smell baseline. **Spec**: the maintainer's request (Forgejo CLI with usage instructions; a clean, separated, standardized workspace per PR; agents able to work on each other's branches cleanly; an open, non-draft PR). ## Standards **P1 (hard, data loss)** - **S1. `scripts/wt` `remove()` / `is_dirty()`.** The dirty check uses `git status --porcelain`, which skips ignored files, and `git worktree remove` then deletes them. Reproduced: a plain `wt rm` (no `-f`) deleted `data/app.db`. `workspaces.md` tells each worktree to create its own DB under the ignored `data/`, so `rm` and `prune` wipe exactly what the doc tells agents to create. *Fix:* treat ignored files outside the known symlinks as dirty (or refuse when `data/`/`logs/` has content). **P2** - **S2. `docs/standards/git-and-workflow.md` was not updated.** It still allows `chore/<topic>`, which AGENTS.md and `wt` (`TYPES="feat fix refactor docs"`) reject. Its §2.1 Draft PRs and §2.3 Branch Cleanup repeat the new AGENTS.md lines and `forgejo-cli.md`, which breaks single source of truth (writing-for-agents §Pruning). - **S3. `cmd_pr` handover.** It checks only for uncommitted changes. A clean worktree with *unpushed* commits passes `merge --ff-only` and is handed over, contradicting "Committed-and-pushed work is the handover point". *Fix:* also require `unpushed == 0`. - **S4. `workspaces.md` repeats the branch-name rule** ("follows AGENTS.md (`feat|fix|refactor|docs/<slug>`); the script refuses anything else"). This caches AGENTS.md and the script's own error message. Replace it with a pointer. **P3** - **S5. Negations where positive targets fit** (writing-for-agents §Negation): - AGENTS.md: "never switch its branch or edit in it", "leave it untouched", "not raw `curl`". - `forgejo-cli.md`: "no reason to hand-build `curl`…", "never inline in the shell". - `workspaces.md`: "never force-push". Keep this one as a guardrail, but pair it with the positive target. - **S6. AGENTS.md sub-bullets repeat `workspaces.md`** (ownership, "end every session committed and pushed"). That material is always loaded but already sits behind a pointer. - **S7. `cmd_new` fetches before validating its arguments.** A bad invocation still hits the network. - **S8. `remove()` treats any non-empty second argument as `--force`**, while its safety checks key only on `-f`. Reject unknown flags. - **S9. `bootstrap()` writes `/.worktrees` to `info/exclude`,** repeating the new `.gitignore` line (possible Duplicated Code). - **S10. `local upstream` sits inside the `prune` loop.** Declare it with `local p branch`. **Held up:** quoting; no `local x=$(...)` masking; `prune` needs a gone upstream *and* ancestry of master, so unpushed and squash-merged branches are kept; `rm` is limited to `.worktrees/`; odd branch names are safe. ## Spec The documented `tea` commands exist in tea 0.16.0, except the one in C4. The merge body fields (`Do`, `MergeTitleField`, `delete_branch_after_merge`) match `MergePullRequestOption`. The PR is open, not WIP. **(a) Missing or partial** - **C1. P2**, *"separated and standarized"*. `git-and-workflow.md` disagrees with the new policy (`chore/*`, no mention of worktrees or `tea`). Same root as S2. - **C2. P2**, *"work on other's stuff if needed in a clean and neat way"*. The same-branch case has only a dirty check. An owner who committed but hasn't pushed, or is between edits, gets a second session in the same tree and HEAD. Same root as S3. A lease marker would make this robust. **(b) Scope creep** - **C3.** None. The symlinks, `info/exclude` and the `issue-tracker.md` rewrite all serve the request. **(c) Implemented but wrong** (*"provide instrucctions of use"*) - **C4. P3.** `tea pr list --labels X`: that flag doesn't exist for `pulls list`; only `issues list` has it. - **C5. P2.** "Read an issue / PR with comments: `tea issues 42` · `tea pr 42`". Comments appear only with `--comments`, and agents run non-interactively, so they miss review comments. - **C6. P3.** "Promote draft → ready": `tea pr edit 42 --ready` strips `WIP:` itself, which is less error-prone than retyping the title. - **C7. P3.** The documented merge title `<PR title> (#N)` matches stated policy, but recent history uses Forgejo's default title. The maintainer should confirm which is intended. - **C8. P3.** No repo `CLAUDE.md`. Codex reads AGENTS.md natively; Claude relies on the harness. A one-line `CLAUDE.md` with `@AGENTS.md` makes discovery reliable. - **C9. P3.** `workspaces.md` "`rm -f`" reads as `wt rm -f <x>`, but only `wt rm <x> -f` works. The wrong order fails safely. --- **Summary.** Standards: 10 findings, worst **S1** (`wt rm`/`prune` silently delete ignored files such as the worktree's own `data/` DB). Spec: 8 findings, worst **C2** (the same-branch handover isn't isolated when the owner has unpushed commits). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix(agents): address code-review findings for PR #139
All checks were successful
CI / lint-and-test (pull_request) Successful in 1m59s
5a4752f0c6
- wt rm/prune keep worktrees holding ignored local files (data/ DB, logs)
  instead of letting `git worktree remove` delete them silently.
- wt pr refuses a worktree or local branch with unpushed commits, matching
  the committed-and-pushed handover rule.
- wt new validates arguments before fetching; wt rm parses -f in either
  position and rejects unknown flags; chore/ joins the branch types.
- git-and-workflow.md becomes the single source for branch naming, PR
  protocol, merge style (`<PR title> (#N)`) and cleanup; AGENTS.md §4 and
  workspaces.md point at it instead of restating it.
- forgejo-cli.md: `--comments` to read comments, `pr edit --ready`,
  `--labels` only on issues; positive phrasing throughout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Author
Owner

Review fixes: 5a4752f

All findings from the review are addressed in this first pass except C8 (reason below).

Standards

# Finding Resolution
S1 wt rm/prune silently delete ignored files Fixed. New local_files() lists ignored files other than the shared symlinks and tool caches. rm refuses and lists them (-f overrides); prune keeps the worktree and says why. Verified: a data/app.db blocks rm; __pycache__ does not.
S2 git-and-workflow.md stale, chore/ conflict, duplication Fixed. That doc is now the single source for branch naming, PR protocol, merge style and cleanup, and it links to workspaces/forgejo-cli. chore added to wt types. AGENTS.md §4 points to it instead of restating.
S3 wt pr hands over worktrees with unpushed commits Fixed. It refuses when the existing worktree, or a leftover local branch, has unpushed commits.
S4 workspaces.md restates branch-name rule Fixed. Replaced with a pointer to git-and-workflow.md.
S5 Negations Fixed. AGENTS.md, forgejo-cli.md and workspaces.md rephrased positively. The force-push guardrail is kept, paired with "preserve both sides' commits".
S6 AGENTS.md sub-bullets repeat workspaces.md Fixed. §4 now holds the rule, the two commands and pointers.
S7 wt new fetches before validating Fixed. Validation runs first.
S8 rm treats any 2nd arg as force Fixed. Real option parsing: -f in any position; unknown flags and extra targets are rejected.
S9 /.worktrees duplicated in info/exclude Fixed. Dropped; .gitignore covers it.
S10 local upstream inside loop Fixed.

Spec

# Finding Resolution
C1 Workflow docs disagree Fixed (see S2).
C2 Same-branch handover with unpushed commits Fixed (see S3). Ownership is now defined as "uncommitted changes or unpushed commits", and wt enforces it. A lease file was left out: the git state already is the lease.
C3 No scope creep No change needed.
C4 pr list --labels doesn't exist Fixed. Table says --labels is issues-only.
C5 Comments need --comments Fixed. tea issues 42 --comments · tea pr 42 --comments.
C6 Use pr edit --ready Fixed.
C7 Merge title convention Confirmed by maintainer: <PR title> (#N). Recorded in git-and-workflow.md §2.3; forgejo-cli.md links to it.
C8 Add CLAUDE.md → @AGENTS.md Not applied. This Claude Code harness already loads AGENTS.md as project instructions (observed in-session), so the import would load it twice and double its always-on context cost.
C9 rm -f argument order Fixed (see S8). Both orders work; docs show wt rm -f <name>.

Verification: wt exercised in throwaway branches (bad names, missing arg, data/ guard, -f either position, unknown flag, unpushed refusal on rm); pre-commit (ruff, pytest) green; scripts/check_docs.py clean.

🤖 Generated with Claude Code

## Review fixes: 5a4752f All findings from the [review](https://git.gaboggamer.online/gabogg/hikcentral/pulls/139#issuecomment-2538) are addressed in this first pass except C8 (reason below). ### Standards | # | Finding | Resolution | |---|---|---| | S1 | `wt rm`/`prune` silently delete ignored files | **Fixed.** New `local_files()` lists ignored files other than the shared symlinks and tool caches. `rm` refuses and lists them (`-f` overrides); `prune` keeps the worktree and says why. Verified: a `data/app.db` blocks `rm`; `__pycache__` does not. | | S2 | `git-and-workflow.md` stale, `chore/` conflict, duplication | **Fixed.** That doc is now the single source for branch naming, PR protocol, merge style and cleanup, and it links to workspaces/forgejo-cli. `chore` added to `wt` types. AGENTS.md §4 points to it instead of restating. | | S3 | `wt pr` hands over worktrees with unpushed commits | **Fixed.** It refuses when the existing worktree, or a leftover local branch, has unpushed commits. | | S4 | `workspaces.md` restates branch-name rule | **Fixed.** Replaced with a pointer to `git-and-workflow.md`. | | S5 | Negations | **Fixed.** AGENTS.md, `forgejo-cli.md` and `workspaces.md` rephrased positively. The force-push guardrail is kept, paired with "preserve both sides' commits". | | S6 | AGENTS.md sub-bullets repeat `workspaces.md` | **Fixed.** §4 now holds the rule, the two commands and pointers. | | S7 | `wt new` fetches before validating | **Fixed.** Validation runs first. | | S8 | `rm` treats any 2nd arg as force | **Fixed.** Real option parsing: `-f` in any position; unknown flags and extra targets are rejected. | | S9 | `/.worktrees` duplicated in `info/exclude` | **Fixed.** Dropped; `.gitignore` covers it. | | S10 | `local upstream` inside loop | **Fixed.** | ### Spec | # | Finding | Resolution | |---|---|---| | C1 | Workflow docs disagree | **Fixed** (see S2). | | C2 | Same-branch handover with unpushed commits | **Fixed** (see S3). Ownership is now defined as "uncommitted changes or unpushed commits", and `wt` enforces it. A lease file was left out: the git state already is the lease. | | C3 | No scope creep | No change needed. | | C4 | `pr list --labels` doesn't exist | **Fixed.** Table says `--labels` is issues-only. | | C5 | Comments need `--comments` | **Fixed.** `tea issues 42 --comments` · `tea pr 42 --comments`. | | C6 | Use `pr edit --ready` | **Fixed.** | | C7 | Merge title convention | **Confirmed by maintainer:** `<PR title> (#N)`. Recorded in `git-and-workflow.md` §2.3; `forgejo-cli.md` links to it. | | C8 | Add `CLAUDE.md` → `@AGENTS.md` | **Not applied.** This Claude Code harness already loads `AGENTS.md` as project instructions (observed in-session), so the import would load it twice and double its always-on context cost. | | C9 | `rm -f` argument order | **Fixed** (see S8). Both orders work; docs show `wt rm -f <name>`. | **Verification:** `wt` exercised in throwaway branches (bad names, missing arg, `data/` guard, `-f` either position, unknown flag, unpushed refusal on `rm`); pre-commit (ruff, pytest) green; `scripts/check_docs.py` clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Author
Owner

Review items not applied (or only partly applied), with reasoning

C8: add a repo CLAUDE.md containing @AGENTS.md: not applied

  • The claim: Claude only picks up AGENTS.md "through the harness", so a CLAUDE.md import would make discovery reliable.
  • What happens in practice: the Claude Code build this repo uses already loads AGENTS.md natively. When a session starts in this repo, its system context includes Contents of .../hikcentral/AGENTS.md (project instructions, checked into the codebase), with no CLAUDE.md present. Codex reads AGENTS.md natively too.
  • Cost of adding it: a CLAUDE.md with @AGENTS.md would put the same ~6 KB into every Claude turn twice. That doubles its context load and gives two entry points for one source of truth, against writing-for-agents' pruning rules, for no gain in discovery.
  • When to revisit: if a Claude environment is found that reads only CLAUDE.md, add the one-line file then. Worth one check whenever the harness changes.

C2: lease / lock marker for same-branch handover: partly applied

  • Applied: the part of C2 that was a defect. Ownership is now "uncommitted changes or unpushed commits", and wt pr enforces it in both places it can occur: an existing worktree, and a leftover local branch with no worktree.
  • Not applied: the suggested lease/lock file, for three reasons:
    • The git state already is the lease. A session that has changed anything shows up as dirty or unpushed, and that is exactly what wt pr refuses.
    • A lock file adds a failure mode git state doesn't have. A crashed session leaves a stale lock that someone has to notice and clear by hand. Git state can't go stale: if the work was pushed, the worktree really is free.
    • The remaining gap is small and visible. Two sessions can share a worktree only in the window after the first one opens a clean worktree and before it edits anything. In that window neither session has work to lose. If a real collision shows up, a lease can be added then.

Verification gap from the fix round, now closed

The previous reply noted wt pr's unpushed-commit refusal had not been exercised, since it needs a PR whose worktree has unpushed commits. It has now been tested with a stand-in tea on PATH that returns a throwaway branch (docs/pr-probe, pushed and then deleted from the remote):

State Result
Clean and pushed Handed over (path printed)
Unpushed commit in the worktree has unpushed commits on 'docs/pr-probe'; its owner must push first
Worktree removed, local branch ahead of remote local 'docs/pr-probe' has unpushed commits from an earlier session; push or drop them first

🤖 Generated with Claude Code

## Review items not applied (or only partly applied), with reasoning ### C8: add a repo `CLAUDE.md` containing `@AGENTS.md`: not applied - **The claim:** Claude only picks up `AGENTS.md` "through the harness", so a `CLAUDE.md` import would make discovery reliable. - **What happens in practice:** the Claude Code build this repo uses already loads `AGENTS.md` natively. When a session starts in this repo, its system context includes `Contents of .../hikcentral/AGENTS.md (project instructions, checked into the codebase)`, with no `CLAUDE.md` present. Codex reads `AGENTS.md` natively too. - **Cost of adding it:** a `CLAUDE.md` with `@AGENTS.md` would put the same ~6 KB into every Claude turn twice. That doubles its context load and gives two entry points for one source of truth, against writing-for-agents' pruning rules, for no gain in discovery. - **When to revisit:** if a Claude environment is found that reads only `CLAUDE.md`, add the one-line file then. Worth one check whenever the harness changes. ### C2: lease / lock marker for same-branch handover: partly applied - **Applied:** the part of C2 that was a defect. Ownership is now "uncommitted changes **or unpushed commits**", and `wt pr` enforces it in both places it can occur: an existing worktree, and a leftover local branch with no worktree. - **Not applied:** the suggested lease/lock file, for three reasons: - **The git state already is the lease.** A session that has changed anything shows up as dirty or unpushed, and that is exactly what `wt pr` refuses. - **A lock file adds a failure mode git state doesn't have.** A crashed session leaves a stale lock that someone has to notice and clear by hand. Git state can't go stale: if the work was pushed, the worktree really is free. - **The remaining gap is small and visible.** Two sessions can share a worktree only in the window after the first one opens a clean worktree and before it edits anything. In that window neither session has work to lose. If a real collision shows up, a lease can be added then. ### Verification gap from the fix round, now closed The previous reply noted `wt pr`'s unpushed-commit refusal had not been exercised, since it needs a PR whose worktree has unpushed commits. It has now been tested with a stand-in `tea` on `PATH` that returns a throwaway branch (`docs/pr-probe`, pushed and then deleted from the remote): | State | Result | |---|---| | Clean and pushed | Handed over (path printed) | | Unpushed commit in the worktree | `has unpushed commits on 'docs/pr-probe'; its owner must push first` | | Worktree removed, local branch ahead of remote | `local 'docs/pr-probe' has unpushed commits from an earlier session; push or drop them first` | 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Author
Owner

Code review, second pass: PR #139 (docs/agent-workspaces @ 5a4752f vs master @ 8f7a80e)

Both axes confirm that every first-pass fix (S1–S10, C1–C9) is correct and introduced no regressions. The declined items (C8 CLAUDE.md, C2 lease file) were not re-raised. No P1/P2 findings. Per the review policy, the P3s below go into a follow-up issue and the PR merges.

Standards

The fixes were exercised on a local throwaway branch:

  • local_files() blocks rm on data/app.db and passes caches and symlinks.
  • rm rejects -x, -- and a second target.
  • new validates its arguments before fetching.
  • The wt pr unpushed and diverged logic is sound.
# Pri Finding
S2-1 P3 scripts/wt local_files() hard-codes ^(\.venv|\.env|scripts/tailwindcss)$, repeating SHARED=(...). A new shared symlink added without updating the regex makes every rm/prune refuse. Build the pattern from SHARED (possible Duplicated Code / Shotgun Surgery).
S2-2 P3 cmd_pr reimplements unpushed() inline (rev-list --count "$branch" --not --remotes=…) without its fallback. Give unpushed a ref argument and reuse it (possible Duplicated Code).
S2-3 P3 cmd_rm accepts . and ..: "$WT_DIR/.." passes both -d and the "$WT_DIR"/* prefix check, so wt rm .. -f reaches git worktree remove --force <main checkout>, which only git's own refusal stops. Canonicalise with realpath before the prefix check.
S2-4 P3 cmd_pr, when a local branch has no worktree: the worktree is added before the --ff-only check, so a divergence leaves an orphan worktree behind. Check first, or remove it on failure.
S2-5 P3 remove() prints "removed … and merged branch X", which should read "deleted merged branch X".
S2-6 P3 forgejo-cli.md: "Installed at ~/.local/bin/tea on the dev workstation" caches the environment (writing-for-agents §Pruning). Drop it.
S2-7 P3 git-and-workflow.md: "Merge commit (no squash or rebase)" names the banned options (writing-for-agents §Negation). Use "Merge commit only".

Spec

Checks against the spec:

  • Every tea command and flag in forgejo-cli.md exists in tea 0.16.0.
  • The repo has default_delete_branch_after_merge=false, so the explicit delete_branch_after_merge:true in the merge call is required.
  • The four docs are consistent.
  • A fresh agent can run the whole flow end to end: wt new, then push -u, tea pr create, review, API merge and wt prune. The gone-upstream path of prune was reproduced.
  • The PR is open and not WIP.
# Pri Finding
C2-1 P3 "without stepping on other's work". forgejo-cli.md §Merging says "Then clean up the local side with scripts/wt prune" in a doc whose PR commands run from the branch's worktree. git worktree remove of the current directory succeeds, so an agent pruning from its own worktree deletes its own cwd. Say "from the main checkout" (or make wt refuse to remove $PWD).
C2-2 P3 "clean and neat way". A branch pushed without -u has no upstream config, so prune silently continues and the worktree stays forever. Print a "kept … no upstream" line, as the ignored-files case already does.

Summary. Standards: 7 findings, all P3; the most useful is S2-3 (rm .. path canonicalisation). Spec: 2 findings, both P3; the most useful is C2-1 (pruning from inside the worktree being removed).

🤖 Generated with Claude Code

# Code review, second pass: PR #139 (`docs/agent-workspaces` @ 5a4752f vs `master` @ 8f7a80e) Both axes confirm that every first-pass fix (S1–S10, C1–C9) is correct and introduced no regressions. The declined items (C8 `CLAUDE.md`, C2 lease file) were not re-raised. **No P1/P2 findings.** Per the review policy, the P3s below go into a follow-up issue and the PR merges. ## Standards The fixes were exercised on a local throwaway branch: - `local_files()` blocks `rm` on `data/app.db` and passes caches and symlinks. - `rm` rejects `-x`, `--` and a second target. - `new` validates its arguments before fetching. - The `wt pr` unpushed and diverged logic is sound. | # | Pri | Finding | |---|---|---| | S2-1 | P3 | `scripts/wt` `local_files()` hard-codes `^(\.venv\|\.env\|scripts/tailwindcss)$`, repeating `SHARED=(...)`. A new shared symlink added without updating the regex makes every `rm`/`prune` refuse. Build the pattern from `SHARED` (possible Duplicated Code / Shotgun Surgery). | | S2-2 | P3 | `cmd_pr` reimplements `unpushed()` inline (`rev-list --count "$branch" --not --remotes=…`) without its fallback. Give `unpushed` a ref argument and reuse it (possible Duplicated Code). | | S2-3 | P3 | `cmd_rm` accepts `.` and `..`: `"$WT_DIR/.."` passes both `-d` and the `"$WT_DIR"/*` prefix check, so `wt rm .. -f` reaches `git worktree remove --force <main checkout>`, which only git's own refusal stops. Canonicalise with `realpath` before the prefix check. | | S2-4 | P3 | `cmd_pr`, when a local branch has no worktree: the worktree is added *before* the `--ff-only` check, so a divergence leaves an orphan worktree behind. Check first, or remove it on failure. | | S2-5 | P3 | `remove()` prints "removed … and merged branch X", which should read "deleted merged branch X". | | S2-6 | P3 | `forgejo-cli.md`: "Installed at `~/.local/bin/tea` on the dev workstation" caches the environment (writing-for-agents §Pruning). Drop it. | | S2-7 | P3 | `git-and-workflow.md`: "Merge commit (no squash or rebase)" names the banned options (writing-for-agents §Negation). Use "Merge commit only". | ## Spec Checks against the spec: - Every `tea` command and flag in `forgejo-cli.md` exists in tea 0.16.0. - The repo has `default_delete_branch_after_merge=false`, so the explicit `delete_branch_after_merge:true` in the merge call is required. - The four docs are consistent. - A fresh agent can run the whole flow end to end: `wt new`, then `push -u`, `tea pr create`, review, API merge and `wt prune`. The gone-upstream path of `prune` was reproduced. - The PR is open and not WIP. | # | Pri | Finding | |---|---|---| | C2-1 | P3 | *"without stepping on other's work"*. `forgejo-cli.md` §Merging says "Then clean up the local side with `scripts/wt prune`" in a doc whose PR commands run from the branch's worktree. `git worktree remove` of the current directory succeeds, so an agent pruning from its own worktree deletes its own cwd. Say "from the main checkout" (or make `wt` refuse to remove `$PWD`). | | C2-2 | P3 | *"clean and neat way"*. A branch pushed without `-u` has no upstream config, so `prune` silently `continue`s and the worktree stays forever. Print a "kept … no upstream" line, as the ignored-files case already does. | --- **Summary.** Standards: 7 findings, all P3; the most useful is **S2-3** (`rm ..` path canonicalisation). Spec: 2 findings, both P3; the most useful is **C2-1** (pruning from inside the worktree being removed). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
gabogg merged commit c096d72ac8 into master 2026-09-26 16:28:37 +00:00
gabogg deleted branch docs/agent-workspaces 2026-09-26 16:28:37 +00:00
Sign in to join this conversation.
No description provided.