docs: record the tag-immutability decision (86cb4jjbv) - #8
Merged
Conversation
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 applied —
Immutable hook version tags, live on this repo since 2026-08-18. It targetsrefs/tags/hooks-v*andrefs/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 cuttinghooks-v1.1works as before.Verified rather than assumed. Pushed a throwaway
hooks-v0.0-rulesettesttag matching the pattern:remote: - Cannot force-push to this tag— rejectedremote: - Cannot delete this tag— rejectedCleaned up afterwards by temporarily adding that one tag to the ruleset's
excludelist, 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-commitpin, so it was tested before being ruled out. Measured with pre-commit 4.6.2 against a config pinned to both SHAs:A plain
autoupdateconverts 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 --freezedoes preserve SHA form, but it is not a "keep my pin" flag: it still bumpedv0.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:
git ls-remote refs/tags/hooks-v1.0yields the tag objectff03601…, not the commit72cfe49…. 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 frommain. If anyone does freeze a rev, prefer the commit.astral-sh/ruff-pre-commituses 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 barepre-commit autoupdateupdates 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 runspre-commit run --all-files, the new linter lands in CI on the same commit. That is precisely what their# Pinned deliberatelycomments exist to stop, arriving as a side effect of an unrelated bump.🤖 Generated with Claude Code