DECISION NEEDED: should pinned Go tools be go install-with-commit-pin, or tracked as go.mod tool dependencies? #37

Open
opened 2026-08-09 17:37:06 +02:00 by clawbot · 2 comments
Collaborator

Split out of #28 rather than legislated inside it. The #28 implementer wrote a fleet-wide rejection of the tool-dependency alternative into canonical policy; the reviewer flagged that as arguably yours rather than an implementer's, and I agree, so the text is being softened to explain the current choice without ruling the alternative out. Nothing is blocked on this.

The question

golangci-lint is installed via a commit-pinned ref:

go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@c0d3ddc9...

The alternative is to track it as a tool dependency in go.mod / go.sum (Go 1.24's tool directive, or the older tools.go blank-import pattern).

What is actually true, verified rather than assumed

  • A commit hash is not a server-mutable tag, so the current form does not violate the hash-pinning rule on its face.
  • go install pkg@version ignores the repo's go.mod and go.sum (go help install). Verification comes from the checksum database, not from anything committed in the repo. The #28 text originally claimed both; the go.sum half was wrong and is being removed.
  • So the two options differ in a way that matters to the hash-pinning rule: a tool dependency pins the hash in-repo, in a file under review; go install @hash relies on an external service the repo does not control and cannot audit offline.
  • The commit currently pinned happens to be the v2.12.2 tag commit, which is why --version reports a clean 2.12.2. A pin moved to an untagged commit reports a pseudo-version instead, which silently breaks version comparison. That trap is documented in #28.

Options

  1. Status quo: go install with a commit-pinned ref. No change anywhere. The linter stays out of the module graph. Verification is via the checksum DB rather than committed hashes, and the untagged-pin trap stays live for whoever next moves the pin.
  2. Tool dependency in go.mod/go.sum. The hash lands in a reviewed, committed file — the strongest reading of the hash-pinning rule, and offline-verifiable. Cost: the linter's dependency tree enters every consuming repo's go.mod and go.sum, which is noisy, can interact with the repo's own dependency versions, and makes go mod tidy output larger. This is the cost the #28 implementer cited when rejecting it, and it is real.
  3. Status quo, plus require the pin to be a tagged commit. Cheap mitigation of the trap without restructuring anything.

Recommendation

Option 3.

Option 2 is the more principled reading of the hash-pinning rule and I do not want to talk you out of it if that rule is meant absolutely. But the linter is genuinely a bootstrap prerequisite rather than part of what the module builds, and dragging its tree into every repo's module graph to gain offline verifiability of a developer tool is a poor trade — especially since the container lint stage already pins by image digest, which is the path that records verdicts.

Option 3 keeps the current structure and closes the one sharp edge that has actually drawn blood in this batch: an untagged pin makes bootstrap reinstall on every run and then hard-fail its own assertion with an error blaming a shadowing binary that does not exist. Making "the pin must be a tagged commit" a stated requirement costs nothing and removes a trap.

If you want option 2, say so — it is mechanical to implement across the Go repos, just noisy.

Split out of #28 rather than legislated inside it. The #28 implementer wrote a fleet-wide rejection of the tool-dependency alternative into canonical policy; the reviewer flagged that as arguably yours rather than an implementer's, and I agree, so the text is being softened to explain the current choice without ruling the alternative out. Nothing is blocked on this. ## The question golangci-lint is installed via a commit-pinned ref: ```sh go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@c0d3ddc9... ``` The alternative is to track it as a tool dependency in `go.mod` / `go.sum` (Go 1.24's `tool` directive, or the older `tools.go` blank-import pattern). ## What is actually true, verified rather than assumed - A commit hash is not a server-mutable tag, so the current form does not violate the hash-pinning rule on its face. - `go install pkg@version` **ignores the repo's `go.mod` and `go.sum`** (`go help install`). Verification comes from the checksum database, not from anything committed in the repo. The #28 text originally claimed both; the `go.sum` half was wrong and is being removed. - So the two options differ in a way that matters to the hash-pinning rule: **a tool dependency pins the hash in-repo, in a file under review; `go install @hash` relies on an external service** the repo does not control and cannot audit offline. - The commit currently pinned happens to be the `v2.12.2` tag commit, which is why `--version` reports a clean `2.12.2`. A pin moved to an untagged commit reports a pseudo-version instead, which silently breaks version comparison. That trap is documented in #28. ## Options 1. **Status quo: `go install` with a commit-pinned ref.** No change anywhere. The linter stays out of the module graph. Verification is via the checksum DB rather than committed hashes, and the untagged-pin trap stays live for whoever next moves the pin. 2. **Tool dependency in `go.mod`/`go.sum`.** The hash lands in a reviewed, committed file — the strongest reading of the hash-pinning rule, and offline-verifiable. Cost: the linter's dependency tree enters every consuming repo's `go.mod` and `go.sum`, which is noisy, can interact with the repo's own dependency versions, and makes `go mod tidy` output larger. This is the cost the #28 implementer cited when rejecting it, and it is real. 3. **Status quo, plus require the pin to be a tagged commit.** Cheap mitigation of the trap without restructuring anything. ## Recommendation **Option 3.** Option 2 is the more principled reading of the hash-pinning rule and I do not want to talk you out of it if that rule is meant absolutely. But the linter is genuinely a bootstrap prerequisite rather than part of what the module builds, and dragging its tree into every repo's module graph to gain offline verifiability of a developer tool is a poor trade — especially since the container lint stage already pins by image digest, which is the path that records verdicts. Option 3 keeps the current structure and closes the one sharp edge that has actually drawn blood in this batch: an untagged pin makes bootstrap reinstall on every run and then hard-fail its own assertion with an error blaming a shadowing binary that does not exist. Making "the pin must be a tagged commit" a stated requirement costs nothing and removes a trap. If you want option 2, say so — it is mechanical to implement across the Go repos, just noisy.
sneak was assigned by clawbot 2026-08-09 17:37:06 +02:00
Author
Collaborator

My option 3 recommendation above is withdrawn. Do not act on it — it was mitigating a problem that no longer exists, and it would not have worked anyway.

I recommended "keep the current form, plus require the pin to be a tagged commit", to close the trap where an untagged pin yields a pseudo-version that breaks version comparison.

Two things have since been measured, and together they kill it:

  1. The comparison was truncating, not the pin. The version parser captured only the numeric prefix (\([0-9][0-9.]*\)), which stops at the first -. With the capture widened to the whole token, a pseudo-version compares exactly like any other string and simply matches. There was never a need for a tagged-pin rule; there was a need for an exact comparison.
  2. A tagged-pin rule would not have caught the dangerous case regardless — a pre-release tag is still a tag. Under the old parser, 2.12.2-rc1 parsed to 2.12.2 and therefore compared equal to a 2.12.2 pin, so bootstrap skipped the install and left the pre-release in place while reporting success. That is the same fails-green shape #28 exists to close, and my proposed rule would have permitted it.

Verified directly rather than taken on report:

in: golangci-lint has version 2.12.2-rc1 built with go1.24
    old parser -> [2.12.2]              (== pin, install SKIPPED)
    new parser -> [2.12.2-rc1]          (!= pin, install RUNS)
in: golangci-lint has version (devel) built with go1.24
    both       -> []                    (unparseable -> reinstall, preserved)

The requirement is gone from the canonical text rather than left standing on a dead justification. What replaces it is a preference, stated with an accurate reason: an untagged pin works fine, but its pseudo-version cannot be known without building once, so a tagged pin is easier to keep in sync — explicitly "not because the comparison cannot handle the alternative".

The actual question in this issue is untouched and still yours: whether pinned Go tools should verify via the checksum database (go install pkg@hash, current) or via hashes committed in-repo (go.mod/go.sum tool dependency). Options 1 and 2 above stand as written. Option 3 should be read as struck.

**My option 3 recommendation above is withdrawn. Do not act on it — it was mitigating a problem that no longer exists, and it would not have worked anyway.** I recommended "keep the current form, plus require the pin to be a tagged commit", to close the trap where an untagged pin yields a pseudo-version that breaks version comparison. Two things have since been measured, and together they kill it: 1. **The comparison was truncating, not the pin.** The version parser captured only the numeric prefix (`\([0-9][0-9.]*\)`), which stops at the first `-`. With the capture widened to the whole token, a pseudo-version compares exactly like any other string and simply matches. There was never a need for a tagged-pin rule; there was a need for an exact comparison. 2. **A tagged-pin rule would not have caught the dangerous case regardless — a pre-release tag is still a tag.** Under the old parser, `2.12.2-rc1` parsed to `2.12.2` and therefore compared **equal** to a `2.12.2` pin, so bootstrap **skipped the install** and left the pre-release in place while reporting success. That is the same fails-green shape #28 exists to close, and my proposed rule would have permitted it. Verified directly rather than taken on report: ``` in: golangci-lint has version 2.12.2-rc1 built with go1.24 old parser -> [2.12.2] (== pin, install SKIPPED) new parser -> [2.12.2-rc1] (!= pin, install RUNS) in: golangci-lint has version (devel) built with go1.24 both -> [] (unparseable -> reinstall, preserved) ``` The requirement is gone from the canonical text rather than left standing on a dead justification. What replaces it is a *preference*, stated with an accurate reason: an untagged pin works fine, but its pseudo-version cannot be known without building once, so a tagged pin is easier to keep in sync — explicitly "not because the comparison cannot handle the alternative". **The actual question in this issue is untouched and still yours:** whether pinned Go tools should verify via the checksum database (`go install pkg@hash`, current) or via hashes committed in-repo (`go.mod`/`go.sum` tool dependency). Options 1 and 2 above stand as written. Option 3 should be read as struck.
Author
Collaborator

Status change, not an answer — the decision is still yours.

The work for #40 (all linting runs in Docker) removes the host install of golangci-lint entirely, so the concrete subject of this issue — how to pin a go installed golangci-lint — no longer exists. The policy paragraph that carried the pin, and the only in-repo reference to this issue, went with it.

The general question may still be live: whether pinned Go tools in general should be tracked as go.mod tool dependencies rather than commit-pinned go install invocations. That applies to any future host tool, just no longer to the linter.

So this is either closeable as moot, or worth rewording to the general form. Recommendation: close it — no host tool currently needs the pin, and reopening the general question when one appears costs nothing.

Status change, not an answer — the decision is still yours. The work for https://git.eeqj.de/sneak/prompts/issues/40 (all linting runs in Docker) removes the host install of golangci-lint entirely, so the concrete subject of this issue — how to pin a `go install`ed golangci-lint — no longer exists. The policy paragraph that carried the pin, and the only in-repo reference to this issue, went with it. The general question may still be live: whether pinned Go tools in general should be tracked as `go.mod` tool dependencies rather than commit-pinned `go install` invocations. That applies to any future host tool, just no longer to the linter. So this is either closeable as moot, or worth rewording to the general form. Recommendation: close it — no host tool currently needs the pin, and reopening the general question when one appears costs nothing.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/prompts#37