| name | tag-audit |
| description | Triage and prune accumulated git safety tags using sort-by-SHA grouping and a per-tag decision tree (duplicate / branch-ref / intermediate-waypoint). Read-only by default; deletes only after explicit user confirmation. Use when stacked-rebase or surgical-fix sessions have accumulated multiple safety tags across namespaces (safety/*, pre-*, post-*, backup-*, stack-*), when /stack-cascade's Step 2 collision check blocks reuse of an OP_ID, or when auditing inherited tag soup. Pairs with /stack-cascade as post-success aftercare. Covers scenarios like: 'clean up safety tags', 'too many backup tags', 'prune accumulated tags', 'tag audit', 'tag-audit', 'safety tag cleanup', 'rollback anchor cleanup', 'which tags can I delete', 'tag namespace collision', 'OP_ID already has tags'. |
| user-invocable | true |
| effort | low |
| allowed-tools | ["Bash","Read"] |
| license | MIT |
| compatibility | Requires git. Shell portability: scripts use quoted array iteration or xargs (bash-unquoted-var word-splitting is broken under zsh). See references/shell-portability.md. |
Tag Audit — triage accumulated safety tags
Stacked-rebase sessions accumulate safety tags fast — each surgical operation
deserves its own rollback anchor, but the cost of regret is high. Once an
operation is validated and pushed, those tags decay into redundancy: they
either duplicate another tag, point at the same SHA as a current branch ref,
or mark an intermediate waypoint no one will ever git reset back to.
This skill audits the safety-tag namespace, surfaces duplicates by sorting
on SHA (not name), proposes deletions with reasons, and deletes only after
explicit confirmation.
When to Use
- After a successful + pushed + CI-green stacked-rebase or surgical-fix
session, when the operation's safety tags are no longer load-bearing.
- When
/stack-cascade Step 2 refuses to proceed because the OP_ID
namespace is non-empty.
- When
git tag -l 'safety/*' 'pre-*' 'post-*' returns more entries than
feels healthy and you want to know which ones still earn their keep.
- When inheriting a workspace from a past session and the tag namespace
is unfamiliar.
When NOT to Use
- During an in-flight cascade or surgical fix — tags are still load-bearing
rollback anchors.
- When the operation hasn't been validated end-to-end (CI green, PRs
reviewed, no surprises from teammates) — the rollback window is still
open.
- On release tags or other semantically-meaningful tags outside the
safety-tag pattern set — narrow the pattern set first.
The Survivor Heuristic
A safety tag earns its keep only when it points at a unique,
semantically-meaningful SHA AND that SHA represents a state you might
genuinely need to git reset back to. Anything else is rollback theater
— the deletion candidate. Step 3 implements this as a three-question
decision tree.
Step 1 — Enumerate tags, sorted by SHA
Sort by SHA (not name). Name-grouped output hides duplicates; SHA-grouped
output exposes them — tags pointing at the same commit cluster together,
making redundancy visible at a glance.
PATTERNS=('safety/*')
(
set -euo pipefail
count=$(git tag -l "${PATTERNS[@]}" | wc -l | tr -d ' ')
echo "# Matched $count tag(s) across patterns: ${PATTERNS[*]}" >&2
[[ "$count" -gt 0 ]] || { echo "# (nothing to audit — check pattern typos or working directory)" >&2; exit 0; }
git tag -l "${PATTERNS[@]}" | while IFS= read -r t; do
sha=$(git rev-parse --short "$t^{commit}") \
|| { echo "rev-parse failed for tag '$t' — aborting audit" >&2; exit 1; }
printf '%s TAG %s\n' "$sha" "$t"
done | sort
)
The default ('safety/*') is intentionally conservative — it matches only
the canonical namespace stack-cascade writes to under the current
convention. Widen it explicitly for cleanup that covers other namespaces:
- Legacy
stack-*-start/* from cascades before the convention change.
- Hand-named anchors from ad-hoc surgical sessions:
pre-mr/*,
pre-port/*, pre-fix-*, post-fix-*, pre-split/*.
- Other safety-tag conventions:
backup-*.
PATTERNS=('safety/*' 'pre-*' 'post-*' 'backup-*' 'stack-*')
The audit is read-only; widening the pattern is cheap. But leave the
widened pattern in place for Step 5's deletion only after the KEEP/DROP
review confirms no release tags or ad-hoc semantic tags slipped in
(pre-1.0, pre-release-*, backup-2024-05, safety-audit-2026-q1).
See "When NOT to Use" above.
Step 2 — Cross-reference branch tips
Enumerate current branch tips by SHA, then interleave them with Step 1's
output so SHA collisions between tags and branch refs are visible
adjacently:
PATTERNS=('safety/*')
(
set -euo pipefail
{
git tag -l "${PATTERNS[@]}" | while IFS= read -r t; do
sha=$(git rev-parse --short "$t^{commit}") \
|| { echo "rev-parse failed for tag '$t' — aborting audit" >&2; exit 1; }
printf '%s TAG %s\n' "$sha" "$t"
done
git for-each-ref refs/heads --format='%(objectname:short) BRANCH %(refname:short)'
} | sort
)
Any TAG line whose SHA matches an adjacent BRANCH line is a deletion
candidate — the branch ref already serves as the anchor.
Step 3 — Decision tree per tag
For each tag in the audit output, apply these three questions in order:
- Does another tag point at this exact SHA? → drop one; keep the
more recent or more descriptive name.
- Does a current branch ref point at this exact SHA? → drop the
tag; the branch ref already anchors that SHA.
- Is this an intermediate waypoint that was useful during operation
but adds no information now? → drop unless it marks a uniquely
meaningful state worth permanently anchoring (root snapshots,
pre-cascade origin SHAs, anchors referenced in narrative docs).
A tag survives only when it has a unique SHA AND no equivalent branch
ref AND marks a state genuinely worth git reset-ing back to.
Step 4 — Propose KEEP/DROP with reasons before deleting
Always show the proposal before any deletion. Example output (with
widened patterns covering an ad-hoc pre-* / post-* session):
Found 21 safety tags. After audit:
KEEP (5 — unique semantically-meaningful SHAs):
3be7b11a pre-split/improved-disabled [root anchor]
4a9fac77 pre-split/pr10 [pre-cascade origin]
e5f1dd7d pre-split/pr11 [pre-cascade origin]
aa71726b pre-split/pr12 [pre-cascade origin]
f675fe31 pre-split/pr13 [pre-cascade origin]
DROP (16):
pre-mr/pr10-cascade [duplicate: same SHA as pre-fix-pr11ci/pr10]
pre-mr/pr11-cascade [duplicate: same SHA as pre-fix-pr11ci/pr11]
pre-fix-pr11ci/pr10 [branch-ref: SHA matches branch pr10]
post-fix-pr11ci/pr11 [branch-ref: SHA matches branch pr11]
post-fix-pr11ci/pr12 [branch-ref: SHA matches branch pr12]
post-fix-pr11ci/pr13 [branch-ref: SHA matches branch pr13]
pre-mr/pr9.6-post-triage [intermediate-waypoint]
pre-mr/pr9.6-post-c5 [intermediate-waypoint]
... (etc)
Proceed with deletion? [y/N]
Reason codes: [duplicate], [branch-ref], [intermediate-waypoint],
or a one-line free-text reason for KEEP entries ([root anchor],
[pre-cascade origin], [narrative anchor]).
Step 5 — Confirm and delete (local)
Two-pass verify-then-mutate under set -euo pipefail. Use quoted array
iteration — bash unquoted-var word-splitting fails silently under zsh
(see references/shell-portability.md).
TAGS_TO_DELETE=(
"pre-mr/pr10-cascade"
"pre-mr/pr11-cascade"
"pre-fix-pr11ci/pr10"
)
(
set -euo pipefail
for t in "${TAGS_TO_DELETE[@]}"; do
git rev-parse --verify "refs/tags/$t" >/dev/null 2>&1 \
|| { echo "Tag '$t' not found — aborting (no tags deleted)" >&2; exit 1; }
done
for t in "${TAGS_TO_DELETE[@]}"; do
git tag -d "$t"
done
)
The verify pass is a pre-flight check: a missing tag name aborts before
any deletion, so the namespace can't be left half-cleaned by a typo or a
stale paste from Step 4. (Concurrent tag mutation by another process
between the verify and mutate passes is still possible — this is shell,
not a transaction. The window is narrow if you're the only writer.)
Step 6 — Remote tag cleanup (opt-in only)
Most safety tags are local-only: git push does not push tags by
default. Skip this step unless the tags were explicitly pushed (e.g.,
via git push --tags or a CI workflow that forwards tags).
When remote cleanup is needed, push deletion refspecs in a single atomic
operation so a partial failure doesn't leave the remote half-cleaned:
TAGS_TO_DELETE=(
"pre-mr/pr10-cascade"
"pre-mr/pr11-cascade"
)
(
set -euo pipefail
[[ ${#TAGS_TO_DELETE[@]} -gt 0 ]] \
|| { echo "TAGS_TO_DELETE is empty — refusing to push" >&2; exit 1; }
refspecs=()
for t in "${TAGS_TO_DELETE[@]}"; do
refspecs+=(":refs/tags/$t")
done
git push --atomic origin "${refspecs[@]}"
)
:refs/tags/<name> is the deletion refspec; --atomic makes the
multi-ref push all-or-nothing.
Common Failure Modes
-
Bash idiom failing silently under zsh — for t in $unquoted_var; do git tag -d $t; done passes the whole string as one arg to git tag -d, which complains about a single-revision-not-found error. The
loop "completes" without deleting anything. Always use quoted array
iteration (for t in "${arr[@]}") or xargs. See
references/shell-portability.md for the full mitigation set.
-
Too-broad pattern matches a release tag — git tag -l '*' is
obviously dangerous, but safety-* accidentally matches a hand-named
safety-audit-2026-q1 tag, or backup-* matches a release naming
scheme. The proposal-before-delete gate at Step 4 is the safety net —
read the KEEP/DROP list before confirming.
-
Deleting a tag whose SHA equals a branch tip — but the branch
itself is the one you wanted to restore — if the cascade's pushed
state turns out to be wrong and you want to roll back, the safety tag
was the rollback anchor. Branch tips can move; safety tags do not.
This is why "rollback window closed" is a precondition: don't audit
until you're committed to the current state.
-
Audit run mid-cascade — running this skill while another git
operation is in progress in the same repo (a rebase paused on
conflict, a cherry-pick mid-application) can delete tags the paused
operation needs for --abort. Check git status first.
Pairing with /stack-cascade
/stack-cascade creates safety tags under
safety/${OP_ID}/before/<branch> (and historically under
stack-${PURPOSE}-start/<branch> before the convention change). Step 2
of /stack-cascade blocks if its OP_ID namespace is non-empty — that
collision-check error names this skill as the remediation. The error
shows a literal git tag -l 'safety/${OP_ID}/*' preview command;
running this skill against that pattern set produces the targeted
DROP list. For legacy stack-*-start/* tags, widen PATTERNS to
include 'stack-*' in Step 1.
/stack-cascade and /rebase-validate compose tightly along the cascade
flow: cascade executes, validate verifies meaning preservation.
/tag-audit is a separate utility that pairs with cascade's tag
namespace as post-success aftercare, but operates independently — it's
useful for any safety-tag accumulation, not just cascades, and its
preconditions (rollback window closed) decouple it from the cascade's
session.