Skip to content

docs: record the tag-immutability decision (86cb4jjbv) - #8

Merged
jonnyspicer merged 1 commit into
mainfrom
docs/tag-immutability
Aug 18, 2026
Merged

docs: record the tag-immutability decision (86cb4jjbv)#8
jonnyspicer merged 1 commit into
mainfrom
docs/tag-immutability

Conversation

@jonnyspicer

Copy link
Copy Markdown
Contributor

Closes ClickUp 86cb4jjbv, whose "done when" was a written decision.

Decision: tag-protection rules (option 1), not SHA pinning (option 2)

The ruleset is already appliedImmutable hook version tags, live on this repo since 2026-08-18. It targets refs/tags/hooks-v* and refs/tags/dead-code-v*, blocks force-pushes and deletions, and has no bypass actors (current_user_can_bypass: never, admins included). Tag creation is untouched, so cutting hooks-v1.1 works as before.

Verified rather than assumed. Pushed a throwaway hooks-v0.0-rulesettest tag matching the pattern:

attempt result
create tag succeeded, as intended
force-move to another commit remote: - Cannot force-push to this tag — rejected
delete tag remote: - Cannot delete this tag — rejected

Cleaned up afterwards by temporarily adding that one tag to the ruleset's exclude list, so the real tags stayed protected throughout, then restoring the exact pattern.

Why not SHA pinning

The ticket's option 2 was the only one that also covers the third-party astral-sh/ruff-pre-commit pin, so it was tested before being ruled out. Measured with pre-commit 4.6.2 against a config pinned to both SHAs:

$ pre-commit autoupdate
[astral-sh/ruff-pre-commit] updating 7c55798a78262d... -> v0.16.3
[offworldlabs/ops]         updating 72cfe49c4ca889... -> hooks-v1.0

A plain autoupdate converts SHA pins straight back to mutable tags — including for ops, where it rewrote a SHA to a tag pointing at the very same commit. The protection would evaporate the first time anyone ran the bump command the README itself recommends, across nine repos, silently.

autoupdate --freeze does preserve SHA form, but it is not a "keep my pin" flag: it still bumped v0.16.2 -> v0.16.3 (frozen). So neither mode holds a deliberate version pin, and option 2 buys immutability at the cost of a trap.

Two smaller findings from the same testing, for the record:

  • These tags are annotated, so git ls-remote refs/tags/hooks-v1.0 yields the tag object ff03601…, not the commit 72cfe49…. pre-commit installs happily from either, so the distinction is harmless here — but the tag object becomes unreachable if the tag ever moves, whereas the commit stays reachable from main. If anyone does freeze a rev, prefer the commit.
  • astral-sh/ruff-pre-commit uses lightweight tags, so the ^{} deref that works for ours returns nothing for theirs. Any tooling that resolves both needs to handle each.

The third-party ruff pin remains a mutable tag and is not fixed by this. It is upstream, so a ruleset cannot reach it; the residual risk is astral-sh moving a released tag, which would be a significant upstream incident and whose blast radius here is a linter rather than a deploy path. Recorded as accepted rather than silently left.

Also in this PR

Scopes the README's bump command to --repo. A bare pre-commit autoupdate updates every repo in a consumer's config, so bumping the ops hook in the six consumers that run ruff through pre-commit would also bump ruff — and since their CI runs pre-commit run --all-files, the new linter lands in CI on the same commit. That is precisely what their # Pinned deliberately comments exist to stop, arriving as a side effect of an unrelated bump.

🤖 Generated with Claude Code

…dvice (86cb4jjbv)

Nine repos pin this repository's hooks by tag, and a git tag is mutable, so
`hooks-v1.0` could be force-moved and would reach every consumer on its next
cache-cold CI run with no diff anywhere to show for it.

Fixed at source with an `Immutable hook version tags` ruleset covering
refs/tags/hooks-v* and refs/tags/dead-code-v*: no force-push, no deletion, no
bypass actors. Verified by pushing a throwaway tag matching the pattern and
confirming both a force-move and a delete are rejected. Creating new tags is
unaffected.

Pinning consumers by commit SHA was the other option and is rejected here:
`pre-commit autoupdate` rewrites SHA pins back to tags, so the protection would
disappear the first time anyone ran the bump command this README recommends.
The ruleset needs no change in any consumer and cannot be undone by one.

Also scopes that bump command with --repo. A bare autoupdate updates every repo
in a consumer's config, so bumping the ops hook in the six consumers that run
ruff through pre-commit would silently bump ruff too — turning a deliberate
version pin into a side effect of an unrelated change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jonnyspicer
jonnyspicer merged commit 5e64a3a into main Aug 18, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant