DECISION NEEDED: canonical script/bootstrap pins NODE_VERSION and YARN_VERSION but policy tells it to skip on any version found — the pins are decorative #33

Open
opened 2026-08-09 16:41:44 +02:00 by clawbot · 0 comments
Collaborator

Split out of #28, which fixes the same shape for golangci-lint. Filed as a decision rather than a defect because current policy explicitly prescribes the behaviour, so an agent should not simply "fix" it.

The situation

script/bootstrap pins:

NODE_VERSION="22.17.0"
YARN_VERSION="1.22.22"

and then:

ensure_node()  { if ! missing node;  then return 0; fi ... }
ensure_yarn()  { if ! missing yarn;  then return 0; fi ... }

missing tests PATH presence, never version. So on any machine that already has some node or some yarn, both pins are inert — exactly the defect #28 reports for golangci-lint, in the canonical file itself.

REPO_POLICIES.md lines 55-61 currently prescribes this: "For node it uses the installed node if present; otherwise it installs a PINNED node version via nvm". So the code matches policy; it is the policy that is in tension with the hash-pinning rule and with "Never install 'latest' or 'lts'; always exact versions" two lines later.

Why it matters

It is the same class as #26, #28 and #30: a mechanism that looks like it guarantees something and does not. A repo whose CI container gets node 22.17.0 while a developer's host has node 24 will see behaviour differences that no one attributes to node, because the pin is right there in the file and reads as authoritative. The --frozen-lockfile install does pin the JS dependency tree, so the exposure is the runtime, not the packages.

Options

  1. Enforce the pin, like #28 does for golangci-lint. Compare node --version against NODE_VERSION; if it differs, install the pin via nvm and use it. Same for yarn via corepack. Consistent with the hash-pinning rule and with the fix landing in #28. Cost: bootstrap starts installing node on developer machines that already had a working one, which is slower and more intrusive, and nvm's shell integration makes "use the pinned one" awkward to guarantee for subsequent non-bootstrap commands.
  2. Keep the current behaviour but stop calling it a pin. Rename to NODE_FALLBACK_VERSION / YARN_FALLBACK_VERSION, and reword the policy to say plainly that the host node is used when present and the version is only a fallback for machines without one. Nothing breaks; the file stops implying a guarantee it does not provide.
  3. Status quo. Leave it; accept that host and container node versions may differ.

Recommendation

Option 2, and it is a close call against option 1.

Option 1 is the principled answer and matches what #28 does for the linter — but the two cases are not actually symmetric. The linter's whole purpose is that local and CI agree on what the gate is, so a skew there produces false greens and wasted rework, which is the harm #28 documents. Node is a runtime whose skew shows up as ordinary bugs caught by tests, and forcing an nvm install on every developer machine to close it buys less than it costs. Option 2 removes the false guarantee, which is the actual defect here, at zero cost and with no blast radius.

If you prefer option 1, say so and it will be implemented instead — the version-compare-and-verify logic from #28 transfers directly.

Not blocking anything: #28 proceeds independently.

Split out of #28, which fixes the same shape for golangci-lint. Filed as a decision rather than a defect because current policy explicitly prescribes the behaviour, so an agent should not simply "fix" it. ## The situation `script/bootstrap` pins: ```sh NODE_VERSION="22.17.0" YARN_VERSION="1.22.22" ``` and then: ```sh ensure_node() { if ! missing node; then return 0; fi ... } ensure_yarn() { if ! missing yarn; then return 0; fi ... } ``` `missing` tests `PATH` presence, never version. So on any machine that already has some node or some yarn, both pins are inert — exactly the defect #28 reports for golangci-lint, in the canonical file itself. `REPO_POLICIES.md` lines 55-61 currently **prescribes** this: "For node it uses the installed node if present; otherwise it installs a PINNED node version via nvm". So the code matches policy; it is the policy that is in tension with the hash-pinning rule and with "Never install 'latest' or 'lts'; always exact versions" two lines later. ## Why it matters It is the same class as #26, #28 and #30: a mechanism that looks like it guarantees something and does not. A repo whose CI container gets node 22.17.0 while a developer's host has node 24 will see behaviour differences that no one attributes to node, because the pin is right there in the file and reads as authoritative. The `--frozen-lockfile` install does pin the JS dependency tree, so the exposure is the runtime, not the packages. ## Options 1. **Enforce the pin, like #28 does for golangci-lint.** Compare `node --version` against `NODE_VERSION`; if it differs, install the pin via nvm and use it. Same for yarn via corepack. Consistent with the hash-pinning rule and with the fix landing in #28. Cost: bootstrap starts installing node on developer machines that already had a working one, which is slower and more intrusive, and nvm's shell integration makes "use the pinned one" awkward to guarantee for subsequent non-bootstrap commands. 2. **Keep the current behaviour but stop calling it a pin.** Rename to `NODE_FALLBACK_VERSION` / `YARN_FALLBACK_VERSION`, and reword the policy to say plainly that the host node is used when present and the version is only a fallback for machines without one. Nothing breaks; the file stops implying a guarantee it does not provide. 3. **Status quo.** Leave it; accept that host and container node versions may differ. ## Recommendation **Option 2**, and it is a close call against option 1. Option 1 is the principled answer and matches what #28 does for the linter — but the two cases are not actually symmetric. The linter's whole purpose is that local and CI agree on what the gate is, so a skew there produces false greens and wasted rework, which is the harm #28 documents. Node is a runtime whose skew shows up as ordinary bugs caught by tests, and forcing an nvm install on every developer machine to close it buys less than it costs. Option 2 removes the false guarantee, which is the actual defect here, at zero cost and with no blast radius. If you prefer option 1, say so and it will be implemented instead — the version-compare-and-verify logic from #28 transfers directly. Not blocking anything: #28 proceeds independently.
sneak was assigned by clawbot 2026-08-09 16:41:44 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/prompts#33