Docs/Reference

Reference

Runbook: repository copies

The refusals of the block-repository-copies hook and of tama worktrees remove, each with where it comes from, what it means and what to do next. The rest of the refusal sentences are in the runbook.

The refusals of the block-repository-copies hook and of tama worktrees remove, each with where it comes from, what it means and what to do next. The rest of the refusal sentences are in the runbook.

#BLOCKED: repository copies are forbidden. 'git worktree add' makes a second checkout; work in the existing checkout instead. Remove existing worktrees with 'tama worktrees remove --root <PATH> --apply'.

From the block-repository-copies hook (tama hook block-repository-copies), on a blocking pre_tool_use:bash binding, when the command you were about to run is git worktree add. It means the tool call was refused before git ran: no new checkout exists, and the repository you are already in is unchanged.

The ban exists because a second checkout is a second, divergent state of the same repository. Work done in it is invisible in the checkout you review, its build artifacts are counted twice, and nobody prunes it: on this machine the boot volume has 12.4 GiB free of 1.8 TB, while one project tree holds 519 GB and one agent scratch root (~/.stado/work) holds 1568 entries and 178 GB. There is no size at which a second checkout becomes correct, so the hook refuses the creation rather than reporting the growth.

First command:

tama worktrees list --root <PATH>

That is read-only. It answers whether worktrees already exist under the root you name, and the second half of the sentence — tama worktrees remove --root <PATH> --apply — is how you remove the ones it found (cli/worktrees/remove). Then do what the sentence says: make the change in the existing checkout, on a branch, in place.

#BLOCKED: cloning a local repository makes a copy of it. Work in the existing checkout at <source> instead.

From the same hook, when the refused command is a git clone whose source is a local path rather than a remote URL. <source> is that local path — the checkout you already have, which the hook names so you do not have to look for it. Cloning a remote is untouched; this is only about copying a repository that is already on the disk.

The ban exists because a local clone is the same divergence as a worktree without git even knowing the two are related: the copy has its own .git directory, so no repository owns it, no git worktree list reports it, and tama worktrees remove cannot remove it. A copy that already exists is not a directory you have to find by hand either: tama copies walks the tree for it and refuses to delete one that carries uncommitted changes, commits absent from every remote, or no origin to compare against.

First command:

tama copies list --root <PATH>

Run it on the root holding <source> to see the copies that already exist there (cli/copies/list), then work in <source> itself.

#BLOCKED: recursively copying a git repository makes a copy of it. Work in the existing checkout instead.

From the same hook, when the refused command is a recursive copy (cp -r, or an equivalent recursive copy) whose source is a git repository. It means the copy never started; nothing was written to the destination.

The ban exists because the result is a directory tree containing a .git directory that git has no record of and no repository owns. It also copies whatever the working tree happened to contain, including untracked and ignored build output — the reason the four largest sub-trees of one project directory on this machine are 72 GB, 68 GB, 50 GB and 47 GB. What already exists is inventoried and removed by tama copies, under the refusals on its remove page.

First command:

tama copies list --root <PATH>

It confirms which checkouts under that root are copies of something else (cli/copies). If you wanted a copy for a throwaway experiment, make it a branch in the existing checkout instead; if you wanted an archive, produce one file with git archive rather than a second repository.

#BLOCKED: cannot tell whether <source> is a git repository, because the path is not literal. Pass the real path so the copy can be checked, or work in the existing checkout instead.

From the same hook, on a recursive copy whose source is not a literal path — the token carries a $ or a backtick, as in cp -r "$REPO" dest. <source> is that operand exactly as you wrote it, unexpanded, because that is all the hook was handed; nothing was copied, the refusal lands before the copy starts.

The check exists because the hook reads the command text, not the shell's expansion of it, while answering "is this source a checkout?" means looking at the source on disk. A literal $REPO stats as nothing, so a check that looked only at the text would find no .git and let cp -r "$REPO" dest through while refusing the same command written with a literal path. An agent putting paths into variables is ordinary, so failing open would leave the ban decorative.

First command:

tama worktrees list --root <PATH>

Passing the real literal path is the way through: the copy is then checked and judged on what is actually there. That is deliberate — the guard cannot prove that a source it cannot examine is not a checkout, so it refuses rather than guessing. If the literal path is a checkout, the other three sentences apply: work in it, in place.

#BLOCKED: a stash in a shared checkout takes other sessions' uncommitted work out of the tree, and a refused rebase leaves it stranded. …

From the same hook, when the command is git stash that saves (bare, push, save, or led by an option such as -u) or carries --autostash on pull, rebase or merge. Nothing was stashed and the working tree is unchanged.

The ban exists because several sessions write to the one checkout at once, so its uncommitted changes are not yours to move. A stash takes all of them: git pull --rebase --autostash stashed other sessions' edits, the rebase then refused because another session wrote a file in between, the stash was never applied back, and their edits were gone from the tree while they were still working.

Deliver without touching anyone else's work: stage only the changes this session wrote, then commit what is staged, naming no paths (the commit guard refuses a commit that would stage whole working-tree paths, which carry other sessions' changes too):

tama stage --session <session> --repo <checkout>
git commit -m "<what changed>"
git pull --no-rebase --no-edit
git push

The merge leaves unrelated uncommitted files where they are and refuses, naming the file, only when both sides changed the same one. Reading or restoring a stash that already exists passes: git stash list, git stash show -p stash@{N}, git stash apply stash@{N}.

#tama worktrees does not know the worktree <path>; --except takes the path of a worktree this pass would otherwise remove.

From tama worktrees remove, exit 2, when a value you passed to --except matches no worktree the pass would otherwise consider. <path> is your value. It means nothing was removed and nothing was previewed: the usage error ends the pass before any selection is acted on.

The check exists because --except is how you keep one worktree while removing the rest, so an unrecognised path must not be ignored — an ignored typo would silently remove the one worktree you named to protect. Comparison is canonicalised, the same way a main worktree path is compared, so a relative path or a symlinked parent still matches; a path under a root you did not name does not, because the pass would never have considered it.

First command:

tama worktrees list --root <PATH>

That is read-only. It prints the exact paths of the linked worktrees this pass can see under the root you name; copy the one you meant into --except and run the removal again (cli/worktrees/remove).

#BLOCKED: this command creates the repository <name>, and only the operator names a new product. …

From the block-unnamed-product hook (tama hook block-unnamed-product), on a blocking pre_tool_use:bash binding, when the command is gh repo create NAME or git init [DIR] (the commands its vocabulary.json declares). A new repository is a new product, and a general order such as following an analysis is not consent to one. The name is the first plain word after those commands, its last path part, or the directory the command runs in. When the operator's own words in the conversation name that product, record them as the Tama session capability {"tool": "repository", "actions": ["create:<name>"]} with the lifetime he named, and the same command passes; otherwise build the capability inside the product that already owns it.

#BLOCKED: this command creates a repository without naming it, and the payload names no working directory to read the name from; …

From the same hook, when gh repo create or git init names no repository and the tool call carries no working directory (neither in its arguments nor beside them), so there is no name to hold consent for. Name the repository on the command line, or run it from the harness that sends the working directory, and the refusal above applies to that name.