docs(audit-git-checkouts): state the submodule removal rules exactly #82
Loading…
Reference in a new issue
No description provided.
Delete branch "docs/audit-wording-followups"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Wording fixes deferred from #81's review rounds 4 and 5; no behavior changes.
The driver's help no longer implies every submodule commit that removal deletes is on a remote-tracking ref, since a commit kept only by an upstream tag also passes. The removal-gates reference now names the
<modules>directory, says that only the removal-time scan covers ignored files inside submodules, and says how to restore a listed Git directory's checkout, nested submodules included.The restore step also keeps a detached HEAD commit that no ref holds.
git submodule update --initchecks out the recorded commit, which would let the rerun pass and removal delete that commit. So the step records HEAD first and checks it out again afterwards, without creating a ref.🤖 Generated with Claude Code
Review
01M3D4QCZWJBXAARR22CWSW3KA— headfb5fb7f1d9507f3f680ec8aeb419efd767ea4fa2Review — j4k-oss/agent-skills @
4a3f3699d5Scope: diff against base tree
19897a019b18Status: dispatched — coverage complete (3/3 slots terminal)
Facts: current review-wide projection
Computed under:
Findings (3)
medium — Submodule restore procedure is packed into one table cell with steps out of execution order
01M3D4T8E0DH02WS0614RQYXQ0skills/audit-git-checkouts/references/removal-gates.md(snippet)low — Same Git directory is named
<modules>/<name>in the log/stash commands and<dir>in rev-parse, defined only after first use01M3D4TKFSGTPJBNEQ56CVK3GZskills/audit-git-checkouts/references/removal-gates.md(snippet)low — Help text says branch refs are never deleted, then that removal deletes submodules' local branches, and drops the condition that gates it
01M3D4V6756SJBB5XE9BC03A55skills/audit-git-checkouts/scripts/audit-checkouts.sh(snippet)Other claims
Coverage
Coverage pass: 01M3D4QD2QVHWQ5AFPGYWWRYAN
Accounting: complete
Slot health: healthy
@ -54,3 +54,3 @@| `judgment/sparse-checkout` | Keep the cone's `skip-worktree` bits: clearing them turns absent files into apparent deletions. Confirm the cone with `git sparse-checkout list`, verify no file outside it is materialized, then remove manually once every other gate holds. || `judgment/precious-ignored-files` | Leave `preciousPaths` in place until the owner disposes of them or authorizes their deletion, then rerun. || `judgment/submodule-local-work` | The listed submodules hold work only this worktree has, or content the driver could not inspect. Show the owner what each holds; rerun once the work is pushed. If the owner authorizes discarding it instead, discard it in the submodule first, then rerun. For a `(not checked out)` entry, first show the owner what `<modules>/<name>` (or the listed worktree-relative path) holds, before anything checks it out: `git --git-dir <modules>/<name> --work-tree <modules>/<name> log --oneline HEAD --branches --tags --not --remotes` and `… stash list` (`--work-tree` overrides the deleted checkout its config names). Then, on their decision, restore the checkout with `git submodule update --init -- <path>` while a Gitlink remains, or delete the directory, and rerun. || `judgment/submodule-local-work` | The listed submodules hold work only this worktree has, or content the driver could not inspect. Show the owner what each holds; rerun once the work is pushed. If the owner authorizes discarding it instead, discard it in the submodule first, then rerun. For a `(not checked out)` entry, first show the owner what `<modules>/<name>` (or the listed worktree-relative path) holds, before anything checks it out: `git --git-dir <modules>/<name> --work-tree <modules>/<name> log --oneline HEAD --branches --tags --not --remotes` and `… stash list` (`--work-tree` overrides the deleted checkout its config names). Then, on their decision, restore the checkout with `git submodule update --init -- <path>` while a Gitlink remains, where `<path>` is the output of `git config -f .gitmodules submodule.<name>.path`, run inside the enclosing submodule for a nested entry, or delete the directory, and rerun. |low — Recovery lookup
submodule.<name>.pathfinds nothing for a nested submodule Git directory absorbed under<modules>, because the driver lists it asdep/modules/innerlens
general-bug· armdefault· tally 1 valid / 0 invalid / 0 uncertainclaim
01M3CZ2VNGVHG3KNG2SZ6K27XKof review01M3CZ037RYN4N2VBVBYGFJVAPlow — Restore step for a
(not checked out)entry buries its either/or choice and leaves unclear which command runs inside the enclosing submodulelens
writing-quality· armdefault· tally 1 valid / 0 invalid / 0 uncertainclaim
01M3CZ3RTNFMJB1FYEA0H268H1of review01M3CZ037RYN4N2VBVBYGFJVAPFixed in
3f5cc8c. I reproduced it: a deinitialized nested submodule is listed asdep/modules/inner, and the.gitmoduleslookup fails for that name. The bullet now says a nested absorbed entry is listed under its parent, and the restore step looks up the text after the last/modules/, run in the enclosing submodule. Following those steps on the reproduction restoredinneronto its existing Git directory.Fixed in
3f5cc8c. The row now states the choice first (delete, or restore while a Gitlink remains). It then says both commands run in the superproject whose.gitmodulesnames the submodule. The lookup uses its own<sub>placeholder instead of reusing<path>.@ -514,0 +508,5 @@# submodule must have no uncommitted files, no stash, no linked# worktree, no branch commit that no remote-tracking ref holds, and# no tag on a non-commit or on a commit no remote-tracking ref holds# unless origin has that tag on the same object, or, for a# lightweight tag, on a tag that peels to the same commit.low — Header comment for
list_submodules_with_local_worksays origin has the tag "on a tag that peels", which misstates the lightweight-tag exceptionlens
writing-quality· armdefault· tally 1 valid / 0 invalid / 0 uncertainclaim
01M3CZ4DJ200HNW38FDBT9H6GKof review01M3CZ037RYN4N2VBVBYGFJVAPFixed in
3f5cc8c. The header now reads "unless origin has that tag on the same object or, for a lightweight tag, has a tag of that name that peels to its commit", which matches the^{}check insubmodule_holds_local_work.@ -54,3 +54,3 @@| `judgment/sparse-checkout` | Keep the cone's `skip-worktree` bits: clearing them turns absent files into apparent deletions. Confirm the cone with `git sparse-checkout list`, verify no file outside it is materialized, then remove manually once every other gate holds. || `judgment/precious-ignored-files` | Leave `preciousPaths` in place until the owner disposes of them or authorizes their deletion, then rerun. || `judgment/submodule-local-work` | The listed submodules hold work only this worktree has, or content the driver could not inspect. Show the owner what each holds; rerun once the work is pushed. If the owner authorizes discarding it instead, discard it in the submodule first, then rerun. For a `(not checked out)` entry, first show the owner what `<modules>/<name>` (or the listed worktree-relative path) holds, before anything checks it out: `git --git-dir <modules>/<name> --work-tree <modules>/<name> log --oneline HEAD --branches --tags --not --remotes` and `… stash list` (`--work-tree` overrides the deleted checkout its config names). Then, on their decision, restore the checkout with `git submodule update --init -- <path>` while a Gitlink remains, or delete the directory, and rerun. || `judgment/submodule-local-work` | The listed submodules hold work only this worktree has, or content the driver could not inspect. Show the owner what each holds; rerun once the work is pushed. If the owner authorizes discarding it instead, discard it in the submodule first, then rerun. For a `(not checked out)` entry, first show the owner what `<modules>/<name>` (or the listed worktree-relative path) holds, before anything checks it out: `git --git-dir <modules>/<name> --work-tree <modules>/<name> log --oneline HEAD --branches --tags --not --remotes` and `… stash list` (`--work-tree` overrides the deleted checkout its config names). Then act on their decision and rerun: delete the directory, or, while a Gitlink remains, restore the checkout. To restore, work in the superproject whose `.gitmodules` names the submodule: the worktree for a top-level entry, the enclosing submodule's checkout for a nested one. There, `git config -f .gitmodules submodule.<sub>.path` prints the checkout path to pass to `git submodule update --init --`. `<sub>` is the entry's text after its last `/modules/`, or the whole entry when it has none. |low — Restore rule for
(not checked out)entries misparses top-level submodule names that contain/modules/lens
general-bug· armdefault· tally 1 valid / 0 invalid / 0 uncertainclaim
01M3CZFXT1DXEPSTTEP19JRA14of review01M3CZC2E4JPWJ8ZW154C54E3WFixed in
7f1aba5. I reproduced it withweb/modules/contrib/foo, which the driver lists under that whole name. The restore step now looks up the whole entry in the worktree's.gitmodulesfirst, and splits only at a/modules/whose left part the current.gitmodulesnames, repeating inside that checkout. Walking the text on a worktree holding bothweb/modules/contrib/fooanddep/modules/innerrestored each one.@ -54,3 +54,3 @@| `judgment/sparse-checkout` | Keep the cone's `skip-worktree` bits: clearing them turns absent files into apparent deletions. Confirm the cone with `git sparse-checkout list`, verify no file outside it is materialized, then remove manually once every other gate holds. || `judgment/precious-ignored-files` | Leave `preciousPaths` in place until the owner disposes of them or authorizes their deletion, then rerun. || `judgment/submodule-local-work` | The listed submodules hold work only this worktree has, or content the driver could not inspect. Show the owner what each holds; rerun once the work is pushed. If the owner authorizes discarding it instead, discard it in the submodule first, then rerun. For a `(not checked out)` entry, first show the owner what `<modules>/<name>` (or the listed worktree-relative path) holds, before anything checks it out: `git --git-dir <modules>/<name> --work-tree <modules>/<name> log --oneline HEAD --branches --tags --not --remotes` and `… stash list` (`--work-tree` overrides the deleted checkout its config names). Then, on their decision, restore the checkout with `git submodule update --init -- <path>` while a Gitlink remains, or delete the directory, and rerun. || `judgment/submodule-local-work` | The listed submodules hold work only this worktree has, or content the driver could not inspect. Show the owner what each holds; rerun once the work is pushed. If the owner authorizes discarding it instead, discard it in the submodule first, then rerun. For a `(not checked out)` entry, first show the owner what `<modules>/<name>` (or the listed worktree-relative path) holds, before anything checks it out: `git --git-dir <modules>/<name> --work-tree <modules>/<name> log --oneline HEAD --branches --tags --not --remotes` and `… stash list` (`--work-tree` overrides the deleted checkout its config names). Then act on their decision and rerun: delete the directory, or, while a Gitlink remains, restore the checkout. To restore, find the superproject whose `.gitmodules` names the submodule. Start in the worktree with `<sub>` set to the entry, or, for a worktree-relative `<checkout>/.git/modules/<rest>` entry, in `<checkout>` with `<rest>`. If `git config -f .gitmodules submodule.<sub>.path` prints a path, run `git submodule update --init -- <that path>` there. Otherwise `<sub>` is `<outer>/modules/<rest>` for a submodule `<outer>` that this `.gitmodules` names: move into the checkout its `submodule.<outer>.path` names and repeat with `<rest>`. A top-level name can itself contain `/modules/`, which is why the whole entry is looked up first. |high — Restoring a deinitialized submodule can strand its unique HEAD commit
lens
writing-quality· armdefault· tally 1 valid / 0 invalid / 0 uncertainclaim
01M3D39C3EA0J25PMG97MRRFPDof review01M3CZN1ZY9NEJB79N679PPV0Alow — Nested submodule restore recipe fails when its parent is deinitialized
lens
general-bug· armdefault· tally 1 valid / 0 invalid / 0 uncertainclaim
01M3D3EWCPE7ES7S1JBEZCEXMSof review01M3CZN1ZY9NEJB79N679PPV0AFixed in
fae7bf6. I reproduced it: afterdeinitandgit submodule update --init, no ref held the detached commit, and the gate'slog HEAD --branches --tags --not --remotescheck came back empty, so a rerun would have allowed removal. The row now says to put such a commit on a branch withgit --git-dir <modules>/<name> branch <branch> HEADbefore restoring. With that step, the restored submodule still lists the commit until it is pushed.This is real, and I'm deferring it to a follow-up PR. The user asked for the round gate from round 3 on, and this is a wording gap rather than a severe bug: the recipe fails loudly (
git configexits 1) instead of touching the wrong submodule.Follow-up fix, in
skills/audit-git-checkouts/references/removal-gates.md,judgment/submodule-local-workrow: before moving into<outer>'s checkout, restore that checkout first when it is absent (git submodule update --init -- <outer path>).@ -54,3 +54,3 @@| `judgment/sparse-checkout` | Keep the cone's `skip-worktree` bits: clearing them turns absent files into apparent deletions. Confirm the cone with `git sparse-checkout list`, verify no file outside it is materialized, then remove manually once every other gate holds. || `judgment/precious-ignored-files` | Leave `preciousPaths` in place until the owner disposes of them or authorizes their deletion, then rerun. || `judgment/submodule-local-work` | The listed submodules hold work only this worktree has, or content the driver could not inspect. Show the owner what each holds; rerun once the work is pushed. If the owner authorizes discarding it instead, discard it in the submodule first, then rerun. For a `(not checked out)` entry, first show the owner what `<modules>/<name>` (or the listed worktree-relative path) holds, before anything checks it out: `git --git-dir <modules>/<name> --work-tree <modules>/<name> log --oneline HEAD --branches --tags --not --remotes` and `… stash list` (`--work-tree` overrides the deleted checkout its config names). Then, on their decision, restore the checkout with `git submodule update --init -- <path>` while a Gitlink remains, or delete the directory, and rerun. || `judgment/submodule-local-work` | The listed submodules hold work only this worktree has, or content the driver could not inspect. Show the owner what each holds; rerun once the work is pushed. If the owner authorizes discarding it instead, discard it in the submodule first, then rerun. For a `(not checked out)` entry, first show the owner what `<modules>/<name>` (or the listed worktree-relative path) holds, before anything checks it out: `git --git-dir <modules>/<name> --work-tree <modules>/<name> log --oneline HEAD --branches --tags --not --remotes` and `… stash list` (`--work-tree` overrides the deleted checkout its config names). Then act on their decision and rerun: delete the directory, or, while a Gitlink remains, restore the checkout. Restoring checks out the recorded commit, so first put a HEAD commit that no ref holds on a branch, or the rerun no longer sees it: `git --git-dir <modules>/<name> branch <branch> HEAD`. To restore, find the superproject whose `.gitmodules` names the submodule. Start in the worktree with `<sub>` set to the entry, or, for a worktree-relative `<checkout>/.git/modules/<rest>` entry, in `<checkout>` with `<rest>`. If `git config -f .gitmodules submodule.<sub>.path` prints a path, run `git submodule update --init -- <that path>` there. Otherwise `<sub>` is `<outer>/modules/<rest>` for a submodule `<outer>` that this `.gitmodules` names: move into the checkout its `submodule.<outer>.path` names and repeat with `<rest>`. A top-level name can itself contain `/modules/`, which is why the whole entry is looked up first. |medium — The data-loss-sensitive restore procedure for
(not checked out)entries is a recursive, ordered procedure packed into one table cell, with its steps out of order and placeholders that break for worktree-relative entrieslens
writing-quality· armdefault· tally 1 valid / 0 invalid / 0 uncertainclaim
01M3D3SPGHYGD4X822NGTQGQW9of review01M3D3MJBNFP4640GZQBAME1FDlow — New restore step tells the agent to create a branch, which SKILL.md's user-work rule forbids outright
lens
general-bug· armdefault· tally 1 valid / 0 invalid / 0 uncertainclaim
01M3D3TKE2M790D15FHW7P5HD6of review01M3D3MJBNFP4640GZQBAME1FDFixed in
972ab44. The conflict was real: an agent obeying SKILL.md's "Never create branches or refs" rule would skip the branch step and lose the commit. The restore step no longer creates a ref. It records HEAD withgit --git-dir <dir> rev-parse HEADand checks that commit out again aftergit submodule update --init. On a reproduction, the restored submodule sat on the unpushed commit, no ref held it, and the superproject showedM dep, so the rerun keeps the worktree.Round 4 of this PR only takes clear, severe bugs, so I'm deferring the restructure to a follow-up PR. Two of its three points are already covered by
972ab44. The step now states the stakes ("removal would then delete it"). Its command also uses<dir>, defined as<modules>/<name>or the listed path, so it works for worktree-relative entries.Follow-up fix, in
skills/audit-git-checkouts/references/removal-gates.md: shrink thejudgment/submodule-local-workrow's(not checked out)text to a link, and move the procedure into its own numbered subsection. Order it: define<dir>without the(not checked out)suffix, inspect, delete or record HEAD, restore by the.gitmoduleslookup, check the recorded HEAD out again, rerun. That same subsection should carry #90129's fix (restore an absent parent checkout before descending).@ -54,3 +54,3 @@| `judgment/sparse-checkout` | Keep the cone's `skip-worktree` bits: clearing them turns absent files into apparent deletions. Confirm the cone with `git sparse-checkout list`, verify no file outside it is materialized, then remove manually once every other gate holds. || `judgment/precious-ignored-files` | Leave `preciousPaths` in place until the owner disposes of them or authorizes their deletion, then rerun. || `judgment/submodule-local-work` | The listed submodules hold work only this worktree has, or content the driver could not inspect. Show the owner what each holds; rerun once the work is pushed. If the owner authorizes discarding it instead, discard it in the submodule first, then rerun. For a `(not checked out)` entry, first show the owner what `<modules>/<name>` (or the listed worktree-relative path) holds, before anything checks it out: `git --git-dir <modules>/<name> --work-tree <modules>/<name> log --oneline HEAD --branches --tags --not --remotes` and `… stash list` (`--work-tree` overrides the deleted checkout its config names). Then, on their decision, restore the checkout with `git submodule update --init -- <path>` while a Gitlink remains, or delete the directory, and rerun. || `judgment/submodule-local-work` | The listed submodules hold work only this worktree has, or content the driver could not inspect. Show the owner what each holds; rerun once the work is pushed. If the owner authorizes discarding it instead, discard it in the submodule first, then rerun. For a `(not checked out)` entry, first show the owner what `<modules>/<name>` (or the listed worktree-relative path) holds, before anything checks it out: `git --git-dir <modules>/<name> --work-tree <modules>/<name> log --oneline HEAD --branches --tags --not --remotes` and `… stash list` (`--work-tree` overrides the deleted checkout its config names). Then act on their decision and rerun: delete the directory, or, while a Gitlink remains, restore the checkout. Restoring checks out the recorded commit, which leaves a HEAD commit that no ref holds unreachable, and removal would then delete it. So first record that HEAD with `git --git-dir <dir> rev-parse HEAD`, where `<dir>` is `<modules>/<name>` or the listed path, and after restoring, check it out again in the restored submodule; the rerun then keeps the worktree until that commit is pushed. To restore, find the superproject whose `.gitmodules` names the submodule. Start in the worktree with `<sub>` set to the entry, or, for a worktree-relative `<checkout>/.git/modules/<rest>` entry, in `<checkout>` with `<rest>`. If `git config -f .gitmodules submodule.<sub>.path` prints a path, run `git submodule update --init -- <that path>` there. Otherwise `<sub>` is `<outer>/modules/<rest>` for a submodule `<outer>` that this `.gitmodules` names: move into the checkout its `submodule.<outer>.path` names and repeat with `<rest>`. A top-level name can itself contain `/modules/`, which is why the whole entry is looked up first. |medium — The steps that prevent data loss when restoring a
(not checked out)submodule are written out of order in one table celllens
writing-quality· armdefault· tally 1 valid / 0 invalid / 0 uncertainclaim
01M3D44DWGPYAXVH9PFHCBAYX3of review01M3D3ZR53XHPZDB0WKN1BF301low — Restore guidance says the worktree is kept only "until that commit is pushed", but moving the restored submodule off its Gitlink makes the superproject dirty, so pushing never makes it removable
lens
general-bug· armdefault· tally 1 valid / 0 invalid / 0 uncertainclaim
01M3D43RXWPNH0CXAP51XXY85Jof review01M3D3ZR53XHPZDB0WKN1BF301low — The unchecked submodule Git directory has two placeholder names in one cell, and the inspection commands cover only one entry form
lens
writing-quality· armdefault· tally 1 valid / 0 invalid / 0 uncertainclaim
01M3D4551HKYT8DTSCB980PKN0of review01M3D3ZR53XHPZDB0WKN1BF301Fixed in
34ca85c, superseding the deferral in #90332. The row now says the rerun keeps the worktree as a dirty tree, because the submodule no longer matches its Gitlink. It becomes removable once the commit is pushed and the owner either commits the new Gitlink or returns the submodule to the recorded commit. That matchesdecide_removal_outcome, whose live-status check returnsexpected/dirty-working-treebefore the submodule gate runs.@ -36,3 +36,2 @@echo "The audited repositories' branch refs and stashes are never deleted. Removing a"echo "worktree deletes its submodules' Git directories, local branches included, once every"echo "commit there is held by a remote-tracking ref."echo "worktree deletes its submodules' Git directories, local branches and tags included."low —
--helpand removal-gates.md list different refs as deleted with a submodule's Git directory ("branches and tags" vs "branches and stash")lens
writing-quality· armdefault· tally 1 valid / 0 invalid / 0 uncertainclaim
01M3D455TTQB9RBR76RRRJM5J4of review01M3D3ZR53XHPZDB0WKN1BF301This is the same restructure as #90254, deferred there to a follow-up PR: move the
(not checked out)procedure out of thejudgment/submodule-local-worktable cell into a numbered subsection ofskills/audit-git-checkouts/references/removal-gates.md, in execution order. Round 5 of this PR only takes clear, severe bugs.This is real, and it's wrong wording I added in
972ab44. After the re-checkout, the superproject listsM dep, anddecide_removal_outcomereturnsexpected/dirty-working-treeat its live-status check before the submodule gate runs, so pushing the commit alone never makes the worktree removable. Nothing is lost, because the worktree stays kept. Round 5 only takes clear, severe bugs, so I'm deferring it to a follow-up PR.Follow-up fix, in
skills/audit-git-checkouts/references/removal-gates.md,judgment/submodule-local-workrow: replace "the rerun then keeps the worktree until that commit is pushed" with "the rerun then keeps the worktree as a dirty tree, because the submodule no longer matches its Gitlink; it becomes removable once the commit is pushed and the owner either commits the new Gitlink or returns the submodule to the recorded commit."This is real, and I'm deferring it to a follow-up PR because round 5 only takes clear, severe bugs. The inspection commands predate this PR (#81), and the extra placeholder came in with
972ab44.Follow-up fix, in
skills/audit-git-checkouts/references/removal-gates.md,judgment/submodule-local-workrow: define<dir>once where the cell first names the directory (<modules>/<name>or the listed worktree-relative path), use it in thelogandstash listcommands, and drop the later redefinition.This is real, and I'm deferring it to a follow-up PR because round 5 only takes clear, severe bugs. Neither list is false, since each is a subset of what the deletion removes, but they should match.
Follow-up fix: use one list in both places, "including their local branches, tags, and stash". That means the
usage()closing sentence inskills/audit-git-checkouts/scripts/audit-checkouts.shand the "Removal also deletes each submodule's Git directory" sentence inskills/audit-git-checkouts/references/removal-gates.md. Also say "The audited repositories' own branch refs and stashes are never deleted" inusage().Round-5 note for review
01M3D3ZR53XHPZDB0WKN1BF301(head972ab44). Claim01M3D455E3SRRMM7ZTT4MS2HANappears only in the report, because it anchors on unchanged lines. It is real: thelist_unchecked_submodule_git_dirsheader comment says entries undermodules/print "as its name there", but a nested absorbed entry prints asdep/modules/inner. Round 5 only takes clear, severe bugs, so I'm deferring it.Follow-up fix, in
skills/audit-git-checkouts/scripts/audit-checkouts.sh: have the comment say those entries print as their path relative tomodules/, which is a top-level submodule's name, or<parent>/modules/<name>for a nested absorbed one.@ -54,3 +54,3 @@| `judgment/sparse-checkout` | Keep the cone's `skip-worktree` bits: clearing them turns absent files into apparent deletions. Confirm the cone with `git sparse-checkout list`, verify no file outside it is materialized, then remove manually once every other gate holds. || `judgment/precious-ignored-files` | Leave `preciousPaths` in place until the owner disposes of them or authorizes their deletion, then rerun. || `judgment/submodule-local-work` | The listed submodules hold work only this worktree has, or content the driver could not inspect. Show the owner what each holds; rerun once the work is pushed. If the owner authorizes discarding it instead, discard it in the submodule first, then rerun. For a `(not checked out)` entry, first show the owner what `<modules>/<name>` (or the listed worktree-relative path) holds, before anything checks it out: `git --git-dir <modules>/<name> --work-tree <modules>/<name> log --oneline HEAD --branches --tags --not --remotes` and `… stash list` (`--work-tree` overrides the deleted checkout its config names). Then, on their decision, restore the checkout with `git submodule update --init -- <path>` while a Gitlink remains, or delete the directory, and rerun. || `judgment/submodule-local-work` | The listed submodules hold work only this worktree has, or content the driver could not inspect. Show the owner what each holds; rerun once the work is pushed. If the owner authorizes discarding it instead, discard it in the submodule first, then rerun. For a `(not checked out)` entry, first show the owner what `<modules>/<name>` (or the listed worktree-relative path) holds, before anything checks it out: `git --git-dir <modules>/<name> --work-tree <modules>/<name> log --oneline HEAD --branches --tags --not --remotes` and `… stash list` (`--work-tree` overrides the deleted checkout its config names). Then act on their decision and rerun: delete the directory, or, while a Gitlink remains, restore the checkout. Restoring checks out the recorded commit, which leaves a HEAD commit that no ref holds unreachable, and removal would then delete it. So first record that HEAD with `git --git-dir <dir> rev-parse HEAD`, where `<dir>` is `<modules>/<name>` or the listed path, and after restoring, check it out again in the restored submodule; the rerun then keeps the worktree as a dirty tree, because the submodule no longer matches its Gitlink; it becomes removable once the commit is pushed and the owner either commits the new Gitlink or returns the submodule to the recorded commit. To restore, find the superproject whose `.gitmodules` names the submodule. Start in the worktree with `<sub>` set to the entry, or, for a worktree-relative `<checkout>/.git/modules/<rest>` entry, in `<checkout>` with `<rest>`. If `git config -f .gitmodules submodule.<sub>.path` prints a path, run `git submodule update --init -- <that path>` there. Otherwise `<sub>` is `<outer>/modules/<rest>` for a submodule `<outer>` that this `.gitmodules` names: move into the checkout its `submodule.<outer>.path` names and repeat with `<rest>`. A top-level name can itself contain `/modules/`, which is why the whole entry is looked up first. |medium —
(not checked out)restore procedure is an out-of-order run-on inside a table cell, so the step that saves HEAD reads after the step that loses itlens
writing-quality· armdefault· tally 1 valid / 0 invalid / 0 uncertainclaim
01M3D4FWW42MD71ZJXTS74ARYAof review01M3D4CN7QPC5CMD03VTX5NFRHlow — Restore guidance says committing the new Gitlink makes the worktree removable, but the containment gate then keeps it
lens
general-bug· armdefault· tally 1 valid / 0 invalid / 0 uncertainclaim
01M3D4GAYBEB0VN9YKQCQ9JHRZof review01M3D4CN7QPC5CMD03VTX5NFRHlow — The recursive name-to-checkout-path lookup for
(not checked out)entries is a deterministic algorithm written as prose rather than emitted by the driverlens
writing-quality· armdefault· tally 1 valid / 0 invalid / 0 uncertainclaim
01M3D4GKAJ4JKHD23KNHZ8PFHXof review01M3D4CN7QPC5CMD03VTX5NFRHFixed in
fb5fb7f. I confirmed it with the real prover: a worktree branch whose only extra commit records the new Gitlink getsnot-provenfromprove-branch-contained.sh(exit 1, no rung), so the driver keeps it asjudgment/containment-not-proven. The row now says returning the submodule to the recorded commit is what makes the worktree removable. Committing the Gitlink keeps it kept until that superproject commit lands inorigin/<default>.This is the same restructure deferred in #90254 and #90313. It's a style change rather than a false statement: the cell already says to record HEAD "first", before restoring. This PR now only takes fixes for false statements it introduced or for data loss, so it stays with the follow-up PR: move the
(not checked out)procedure inskills/audit-git-checkouts/references/removal-gates.mdinto a numbered subsection in execution order.Acknowledged, not taken here. Having the driver print each entry's restore command would change behavior, and this PR is wording-only. This PR now only takes fixes for false statements it introduced or for data loss.
Follow-up: in
skills/audit-git-checkouts/scripts/audit-checkouts.sh, havelist_unchecked_submodule_git_dirsresolve each entry's owning superproject and checkout path (or report that no Gitlink remains) and print it with the entry. Then replace the lookup prose in thejudgment/submodule-local-workrow ofreferences/removal-gates.mdwith "restore it with the command the entry names".@ -54,3 +54,3 @@| `judgment/sparse-checkout` | Keep the cone's `skip-worktree` bits: clearing them turns absent files into apparent deletions. Confirm the cone with `git sparse-checkout list`, verify no file outside it is materialized, then remove manually once every other gate holds. || `judgment/precious-ignored-files` | Leave `preciousPaths` in place until the owner disposes of them or authorizes their deletion, then rerun. || `judgment/submodule-local-work` | The listed submodules hold work only this worktree has, or content the driver could not inspect. Show the owner what each holds; rerun once the work is pushed. If the owner authorizes discarding it instead, discard it in the submodule first, then rerun. For a `(not checked out)` entry, first show the owner what `<modules>/<name>` (or the listed worktree-relative path) holds, before anything checks it out: `git --git-dir <modules>/<name> --work-tree <modules>/<name> log --oneline HEAD --branches --tags --not --remotes` and `… stash list` (`--work-tree` overrides the deleted checkout its config names). Then, on their decision, restore the checkout with `git submodule update --init -- <path>` while a Gitlink remains, or delete the directory, and rerun. || `judgment/submodule-local-work` | The listed submodules hold work only this worktree has, or content the driver could not inspect. Show the owner what each holds; rerun once the work is pushed. If the owner authorizes discarding it instead, discard it in the submodule first, then rerun. For a `(not checked out)` entry, first show the owner what `<modules>/<name>` (or the listed worktree-relative path) holds, before anything checks it out: `git --git-dir <modules>/<name> --work-tree <modules>/<name> log --oneline HEAD --branches --tags --not --remotes` and `… stash list` (`--work-tree` overrides the deleted checkout its config names). Then act on their decision and rerun: delete the directory, or, while a Gitlink remains, restore the checkout. Restoring checks out the recorded commit, which leaves a HEAD commit that no ref holds unreachable, and removal would then delete it. So first record that HEAD with `git --git-dir <dir> rev-parse HEAD`, where `<dir>` is `<modules>/<name>` or the listed path, and after restoring, check it out again in the restored submodule; the rerun then keeps the worktree as a dirty tree, because the submodule no longer matches its Gitlink; it becomes removable once the commit is pushed and the owner returns the submodule to the recorded commit. Committing the new Gitlink instead leaves the worktree kept as `judgment/containment-not-proven` until that superproject commit lands in `origin/<default>`. To restore, find the superproject whose `.gitmodules` names the submodule. Start in the worktree with `<sub>` set to the entry, or, for a worktree-relative `<checkout>/.git/modules/<rest>` entry, in `<checkout>` with `<rest>`. If `git config -f .gitmodules submodule.<sub>.path` prints a path, run `git submodule update --init -- <that path>` there. Otherwise `<sub>` is `<outer>/modules/<rest>` for a submodule `<outer>` that this `.gitmodules` names: move into the checkout its `submodule.<outer>.path` names and repeat with `<rest>`. A top-level name can itself contain `/modules/`, which is why the whole entry is looked up first. |medium — Submodule restore procedure is packed into one table cell with steps out of execution order
lens
writing-quality· armdefault· tally 1 valid / 0 invalid / 0 uncertainclaim
01M3D4T8E0DH02WS0614RQYXQ0of review01M3D4QCZWJBXAARR22CWSW3KAlow — Same Git directory is named
<modules>/<name>in the log/stash commands and<dir>in rev-parse, defined only after first uselens
writing-quality· armdefault· tally 1 valid / 0 invalid / 0 uncertainclaim
01M3D4TKFSGTPJBNEQ56CVK3GZof review01M3D4QCZWJBXAARR22CWSW3KA@ -36,3 +36,2 @@echo "The audited repositories' branch refs and stashes are never deleted. Removing a"echo "worktree deletes its submodules' Git directories, local branches included, once every"echo "commit there is held by a remote-tracking ref."echo "worktree deletes its submodules' Git directories, local branches and tags included."low — Help text says branch refs are never deleted, then that removal deletes submodules' local branches, and drops the condition that gates it
lens
writing-quality· armdefault· tally 1 valid / 0 invalid / 0 uncertainclaim
01M3D4V6756SJBB5XE9BC03A55of review01M3D4QCZWJBXAARR22CWSW3KASame restructure as #90254, #90313 and #90378, and it stays with the follow-up PR. The cell already says to record HEAD "first", so this is ordering and packaging rather than a false statement or data loss, which is all this PR now takes. The follow-up's numbered subsection should also name the rerun outcomes this finding lists:
expected/dirty-working-tree, orjudgment/containment-not-provenafter a committed Gitlink.Same as #90315, deferred to the follow-up PR: in
skills/audit-git-checkouts/references/removal-gates.md, define<dir>once where the(not checked out)entry is introduced, and use it in thelog, a spelled-outstash list, andrev-parsecommands. It's a naming inconsistency, not a false statement, and this PR now only takes those or data-loss fixes.Overlaps #90316, deferred to the follow-up PR. As you note, the behavior isn't in question; neither sentence is false, and this PR now only takes fixes for false statements or data loss. Follow-up, in
usage()ofskills/audit-git-checkouts/scripts/audit-checkouts.sh: "Branch refs and stashes in the audited checkouts themselves are never deleted. Removing a worktree also deletes its submodules' Git directories, including their local branches, tags, and stash, once the refusals above find nothing only that submodule holds." Use the same list inreferences/removal-gates.md.