docs(agents): one workspace per agent run — the shared checkout is off-limits #56

Merged
jakman85 merged 1 commits from docs/agents-work-in-their-own-workspace into main 2026-10-06 03:49:28 +00:00
Owner

The problem

Agents have been doing their work in the operator's checkout at
/var/home/keith/source/home-network-k3s-impl and clobbering each other. Nothing in the tree
told them not to. Worktrees appeared in CLAUDE.md only as §3's gate workarounds — i.e. as an
awkward edge case to survive — and decisions/README.md described worktree isolation as
something agents already do rather than as a rule they must follow. The rule itself was written
down nowhere.

What this changes

Documentation only. No code, no manifests, no pipeline logic.

  • CLAUDE.md: new ## 0. Work in your OWN workspace. Never in the shared checkout. Placed
    before §1 so that every §1/§2b/§3/§7 reference elsewhere in the repo and in the
    handoff log keeps its meaning. Three commands, an explicit do-not list for the shared
    checkout, the measurements, "put it outside the repo root", cleanup, what to do if you are
    already standing in the shared checkout with edits, and what a PM must put in a brief it
    writes for another agent.
  • CLAUDE.md §3 reframed: a worktree gate run is now the normal path, not an edge case. The
    breakage list grew from two to three — spec-immutability's skip is avoidable, not merely
    tolerated, if the ENGINE shim also mounts the sibling spec repo read-only with
    -e SPEC_REPO=/home-network-k3s. And the rc-3 paragraph now names both expected skips:
    gitea-settings needs a CI-only token, so a local run cannot return 0 at all.
  • CLAUDE.md §4: one branch, one workspace, one PR.
  • README.md: a fourth non-negotiable rule, and "Working on this repo" now opens with how
    to get out of the checkout before describing the container that runs in it.
  • decisions/README.md: cites §0 as the requirement instead of describing the practice.

Measured, in the shared checkout, 2026-10-05

Every number in the new §0 table was read off the thing itself (CLAUDE.md §2b), not recalled:

Claim Value
checkout: moving entries in its reflog 107
branch it was sitting on feat/pm-agent-issue-to-pr — not main
other runs' uncommitted trees left in it 2 (infra/agent/, platform/pm-agent/), mtimes 5 min apart
its local main vs origin/main 4386d14 vs 22c94ef — 2 behind
registered worktrees / already merged 42 branches, 31 merged
gate scripts carrying a .claude/ exclusion 9 — an in-tree worktree made find discovery count the same manifest up to 18 times

One claim of mine was wrong and is corrected in the doc. Earlier briefs recorded
git worktree remove as blocked by the agent sandbox. Provoked rather than assumed: created a
throwaway detached worktree and removed it — rc 0. §0 now tells agents to try it, and the
rework/<pr-number> hatch is scoped to someone else's stale worktree holding a branch, which
is a real and separate problem (31 of them).

Gate

rc 3 — UNDETERMINED. I am not calling this green. Pinned toolbox, from a worktree,
DEV_KUBECONFIG=/dev/null + a two-mount ENGINE shim:

stage spec-immutability: pass     <- genuinely RAN; the second mount is what makes it run
stage decisions-log:     pass
stage lint:              pass
stage l0:                pass
stage render-golden:     pass
stage l1:                pass
stage approval-policy:   pass     <- but N/A: event='none', it cannot see a PR locally
stage gitea-settings:    SKIPPED  <- SETTINGS_API_TOKEN exists only in CI
stage conformance:       SKIPPED  <- CLUSTER_AVAILABLE!=true; no cluster-needing req touched
MERGE GATE: UNDETERMINED — stages that did not run: gitea-settings

gitea-settings is the only non-cluster skip and it cannot be made to run from a workstation,
so rc 3 is the ceiling for any local run, not a defect in this branch. Read the CI gate, not this.

This PR needs a human, and it is not a formality

CLAUDE.md is in infra/gitea/approval-policy.json's sensitive_paths ("the delivery contract
agents follow"), so approval-policy will be RED in CI until either a non-author posts an
approving review, or an administrator posts

/approve-override <reason>

as a PR review of type "Comment" — not in the plain comment box, which is invisible to CI on
today's token scopes (measured on PR #38).

I have not posted that override and will not. gitea-admin holds owner here and could
clear it, but CLAUDE.md §1 is explicit that an agent doing so on its own PR is exactly the
self-approval the stage exists to prevent.

Not done, deliberately

§0 is prose and nothing enforces it. There is no gate stage that detects work done in the
shared checkout, no check that a workspace is outside the repo root, no check that a branch was
cut from origin/main. Every other rule agents kept breaking (DECISIONS-LOG.md appends, the
three handoff headings) eventually became a gate stage, and that is the honest follow-up here. I
did not write one: the ask was documentation, and an enforcement stage lands in ci/**, itself
a sensitive path, and deserves its own PR.

Also left alone: the 31 stale merged worktrees and the 35 nested under .claude/worktrees/
(other sessions' workspaces — operator action), the two untracked trees in the shared checkout
(another run's in-flight work), and docs/plans/ssd-etcd-execution-runbook.md:18, which still
names an in-repo worktree path but is marked COMPLETED/historical and describes the
decommissioned dev cluster, so that path is a record rather than an instruction.

Handoff entry: decisions/2026-10-05-agents-work-in-their-own-workspace.md.

🤖 Generated with Claude Code

## The problem Agents have been doing their work in the operator's checkout at `/var/home/keith/source/home-network-k3s-impl` and clobbering each other. Nothing in the tree told them not to. Worktrees appeared in `CLAUDE.md` only as §3's *gate workarounds* — i.e. as an awkward edge case to survive — and `decisions/README.md` described worktree isolation as something agents already do rather than as a rule they must follow. The rule itself was written down nowhere. ## What this changes Documentation only. No code, no manifests, no pipeline logic. - **`CLAUDE.md`: new `## 0. Work in your OWN workspace. Never in the shared checkout.`** Placed *before* §1 so that every `§1`/`§2b`/`§3`/`§7` reference elsewhere in the repo and in the handoff log keeps its meaning. Three commands, an explicit do-not list for the shared checkout, the measurements, "put it outside the repo root", cleanup, what to do if you are already standing in the shared checkout with edits, and what a PM must put in a brief it writes for another agent. - **`CLAUDE.md` §3** reframed: a worktree gate run is now the normal path, not an edge case. The breakage list grew from two to three — `spec-immutability`'s skip is *avoidable*, not merely tolerated, if the `ENGINE` shim also mounts the sibling spec repo read-only with `-e SPEC_REPO=/home-network-k3s`. And the rc-3 paragraph now names **both** expected skips: `gitea-settings` needs a CI-only token, so a local run cannot return 0 at all. - **`CLAUDE.md` §4**: one branch, one workspace, one PR. - **`README.md`**: a fourth non-negotiable rule, and "Working on this repo" now opens with how to get *out* of the checkout before describing the container that runs in it. - **`decisions/README.md`**: cites §0 as the requirement instead of describing the practice. ## Measured, in the shared checkout, 2026-10-05 Every number in the new §0 table was read off the thing itself (CLAUDE.md §2b), not recalled: | Claim | Value | |---|---| | `checkout: moving` entries in its reflog | **107** | | branch it was sitting on | `feat/pm-agent-issue-to-pr` — not `main` | | other runs' uncommitted trees left in it | **2** (`infra/agent/`, `platform/pm-agent/`), mtimes 5 min apart | | its local `main` vs `origin/main` | `4386d14` vs `22c94ef` — **2 behind** | | registered worktrees / already merged | **42** branches, **31** merged | | gate scripts carrying a `.claude/` exclusion | **9** — an in-tree worktree made `find` discovery count the same manifest up to **18 times** | **One claim of mine was wrong and is corrected in the doc.** Earlier briefs recorded `git worktree remove` as blocked by the agent sandbox. Provoked rather than assumed: created a throwaway detached worktree and removed it — **rc 0**. §0 now tells agents to try it, and the `rework/<pr-number>` hatch is scoped to *someone else's* stale worktree holding a branch, which is a real and separate problem (31 of them). ## Gate **rc 3 — UNDETERMINED. I am not calling this green.** Pinned toolbox, from a worktree, `DEV_KUBECONFIG=/dev/null` + a two-mount `ENGINE` shim: ``` stage spec-immutability: pass <- genuinely RAN; the second mount is what makes it run stage decisions-log: pass stage lint: pass stage l0: pass stage render-golden: pass stage l1: pass stage approval-policy: pass <- but N/A: event='none', it cannot see a PR locally stage gitea-settings: SKIPPED <- SETTINGS_API_TOKEN exists only in CI stage conformance: SKIPPED <- CLUSTER_AVAILABLE!=true; no cluster-needing req touched MERGE GATE: UNDETERMINED — stages that did not run: gitea-settings ``` `gitea-settings` is the only non-cluster skip and it cannot be made to run from a workstation, so rc 3 is the ceiling for any local run, not a defect in this branch. Read the CI gate, not this. ## This PR needs a human, and it is not a formality `CLAUDE.md` is in `infra/gitea/approval-policy.json`'s `sensitive_paths` ("the delivery contract agents follow"), so **`approval-policy` will be RED in CI** until either a non-author posts an approving review, or an administrator posts /approve-override <reason> **as a PR review of type "Comment"** — not in the plain comment box, which is invisible to CI on today's token scopes (measured on PR #38). **I have not posted that override and will not.** `gitea-admin` holds `owner` here and could clear it, but CLAUDE.md §1 is explicit that an agent doing so on its own PR is exactly the self-approval the stage exists to prevent. ## Not done, deliberately **§0 is prose and nothing enforces it.** There is no gate stage that detects work done in the shared checkout, no check that a workspace is outside the repo root, no check that a branch was cut from `origin/main`. Every other rule agents kept breaking (`DECISIONS-LOG.md` appends, the three handoff headings) eventually became a gate stage, and that is the honest follow-up here. I did not write one: the ask was documentation, and an enforcement stage lands in `ci/**`, itself a sensitive path, and deserves its own PR. Also left alone: the 31 stale merged worktrees and the 35 nested under `.claude/worktrees/` (other sessions' workspaces — operator action), the two untracked trees in the shared checkout (another run's in-flight work), and `docs/plans/ssd-etcd-execution-runbook.md:18`, which still names an in-repo worktree path but is marked COMPLETED/historical and describes the decommissioned dev cluster, so that path is a record rather than an instruction. Handoff entry: `decisions/2026-10-05-agents-work-in-their-own-workspace.md`. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
gitea-admin added 1 commit 2026-10-06 03:21:08 +00:00
docs(agents): one workspace per agent run — the shared checkout is off-limits
gate / gate (pull_request) Failing after 1m47s
gate / conformance (pull_request) Has been skipped
b988da8b77
Agents have been working in the operator's checkout and clobbering each other.
Nothing in the tree told them not to: worktrees appeared in CLAUDE.md only as
§3's gate workarounds, and decisions/README.md described worktree isolation as
something agents already do rather than as a rule.

New CLAUDE.md §0 states the rule, with the commands and an explicit do-not list
for the shared checkout. Placed before §1 so every existing §-reference in the
repo keeps its meaning.

Measured in the shared checkout on 2026-10-05, each row a distinct failure mode:
107 `checkout: moving` reflog entries; sitting on feat/pm-agent-issue-to-pr, not
main; two other runs' untracked trees left in it; local main two commits behind
origin/main; 42 registered worktrees of which 31 are on already-merged branches;
9 gate scripts carrying a `.claude/` exclusion because an in-tree worktree made
find-based discovery count the same manifest up to 18 times.

Corrects a claim carried in earlier briefs: `git worktree remove` is NOT blocked
by the agent sandbox. Provoked on a throwaway worktree — rc 0.

§3 reframed as the normal path and grown from two breakages to three:
spec-immutability's skip is avoidable, not merely tolerated, with the spec repo
mounted read-only in the same ENGINE shim plus -e SPEC_REPO=/home-network-k3s.

CLAUDE.md is a sensitive path, so this PR needs a non-author review or an
administrator /approve-override posted as a PR review. Not posted by me: §1
forbids an agent clearing that on its own PR.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
gitea-admin force-pushed docs/agents-work-in-their-own-workspace from b988da8b77 to 4d6f669c60 2026-10-06 03:23:51 +00:00 Compare
gitea-admin force-pushed docs/agents-work-in-their-own-workspace from 4d6f669c60 to a795cb8b9a 2026-10-06 03:28:22 +00:00 Compare
gitea-admin force-pushed docs/agents-work-in-their-own-workspace from a795cb8b9a to 2cb891c05d 2026-10-06 03:33:20 +00:00 Compare
jakman85 approved these changes 2026-10-06 03:39:38 +00:00
jakman85 merged commit 3ee663dbcc into main 2026-10-06 03:49:28 +00:00
Sign in to join this conversation.
No Reviewers
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Home-Network/home-network-k3s-impl#56