Skip to content

fix(cloud-environment): install gh from the pinned upstream release asset - #533

Merged
kyle-sexton merged 3 commits into
mainfrom
fix/cloud-gh-version-parity
Sep 7, 2026
Merged

kyle-sexton merged 3 commits into
mainfrom
fix/cloud-gh-version-parity

Conversation

@kyle-sexton

Copy link
Copy Markdown
Contributor

No related issue: infrastructure parity found during PR-merge-lane operation.

Summary

The org runs gh in three lanes, and only two of them pin it:

Lane Source Version
Local dev machines melodic-software/dotfiles → mise (dot_config/mise/create_mise.lock) 2.98.0
Self-hosted CI runner melodic-software/ci-runner Dockerfile (ARG GH_VERSION / ARG GH_SHA256) 2.98.0
Claude Code cloud sessions this component's setup.sh, apt-get install -y gh 2.45.0

apt-get install -y gh takes whatever Ubuntu's archive ships. In a live cloud
session on this fleet that is gh version 2.45.0 (2025-07-18 Ubuntu 2.45.0-1ubuntu0.3) — 53 minor versions behind the other two lanes. Every
script and hook that shells out to gh therefore ran against a different CLI
depending on where it ran, and a cloud session was the one place that gap was
invisible.

The comment the apt install carried named cli.github.com, the newer upstream
apt repo, as the reason: it is not on the platform's default allowlist. That is
still true, and this change does not touch it — a release asset from
github.com is not an apt repo, and github.com is on the default allowlist.

Fix

components/cloud-environment/setup.sh now installs the same checksummed
release asset the CI runner image does, in the same style:

  • GH_VERSION / GH_SHA256 declared next to the other fleet pins, with a
    comment naming the two lanes they must stay in lockstep with.
  • curl --fail --location --proto '=https' --tlsv1.2 for
    gh_2.98.0_linux_amd64.tar.gz, sha256sum --check --strict against the pin,
    tar --strip-components=2 of gh_2.98.0_linux_amd64/bin/gh into
    /usr/local/bin (where the script already symlinks dotnet), chmod 0755,
    then the tarball removed — inline and again in the script's end-of-run temp
    sweep.
  • amd64 only, mirroring ci-runner's Dockerfile: the pin is the hash of that
    one asset, and a non-x86_64 VM takes a logged WARN skip rather than an
    unverified download.
  • The script's own conventions are preserved throughout: every failure path is
    a WARN line to the track log, no step is fail-closed, and the script still
    exits 0. SCRIPT_VERSION bumped to 2026-09-05.1 so the completion stamp
    identifies the new build.

README.md gains a "The gh pin" section (why not apt, the three-lane
lockstep, the amd64 and best-effort caveats) and an update-lifecycle bullet
noting that, unlike .NET and Node, gh is not a warm cache — no repo
bootstrap reinstalls it, so this script is the only thing holding cloud
sessions at the fleet version.

setup.test.sh gains six assertions: no apt-get install -y gh (regression
guard), GH_VERSION declared, GH_SHA256 declared as 64 hex characters, the
fetch URL is the pinned cli/cli release asset, the asset is checksum-verified
before install, and the README documents the same version the script installs.

Verification

  • shellcheck --rcfile .shellcheckrc on setup.sh and setup.test.sh: clean.
  • bash harness/shell/run-tests.sh components/cloud-environment/setup.test.sh components/cloud-bootstrap/cloud-bootstrap.test.sh: 2 passed, 0 failed, 0
    skipped — 17 assertions, including the 6 new ones.
  • npm run lint:md: 0 issues in 122 files.
  • shfmt -d, typos, editorconfig-checker: clean. The full lefthook
    pre-commit set (editorconfig, typos, markdownlint, shellcheck, gitleaks) ran
    green on the commit.
  • The pin was authenticated, not assumed. Downloaded
    https://github.com/cli/cli/releases/download/v2.98.0/gh_2.98.0_linux_amd64.tar.gz
    from a live cloud session: sha256sum returns
    3b8ac6b30336802fc1a858d7c084e11cdf24ac1a761ca90b68022d7d729208de, matching
    ci-runner's ARG GH_SHA256 exactly, and the extracted binary reports
    gh version 2.98.0 (2026-08-20).

Network risk, checked rather than guessed

The blocker the old comment named is about cli.github.com's apt repo, not
about raw release downloads, so it does not apply here. Two independent pieces
of evidence say the raw download works in this execution context:

  1. The download above succeeded from a live cloud session on this fleet.
  2. The repo bootstrap that this same script bakes into the snapshot
    (.claude/cloud-bootstrap.sh in claude-code-plugins) already fetches
    shellcheck, actionlint, typos, editorconfig-checker, gitleaks, and shfmt
    from /releases/download/ URLs with pinned SHA-256s, and has done so from
    inside the cache build.

The residual risk is narrow and worth stating: the GitHub proxy's repository
scope can return 403 for release assets from repositories not attached to a
session, and that applies at every network access level. cli/cli is not an
attached repository. If a future cache build hits that, the step logs WARN gh 2.98.0 download failed, the build still exits 0, and the verification
checklist catches the missing binary — the same failure mode the apt install
already had, minus the silent wrong version. Both the script header and the
README record this so the next reader does not have to re-derive it.

Related

  • melodic-software/ci-runner Dockerfile — the ARG GH_VERSION / ARG GH_SHA256 pair this change matches.
  • melodic-software/dotfiles .chezmoidata/packages.yaml and
    dot_config/mise/create_mise.lock — the local-machine lane at the same
    version.
  • melodic-software/claude-code-plugins docs/CLOUD-FLEET-SETUP.md — the
    fleet guide's install description and verification checklist are updated in a
    companion PR to match this install method and to assert a minimum gh
    version instead of mere presence.
  • melodic-software/claude-code-plugins#2654 — the cloud-environment blockers
    whose network findings this change reuses.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VPLatLkg4329L8eyfxhuMa


Generated by Claude Code

…sset

Cloud sessions installed `gh` with `apt-get install -y gh`, which pulls
whatever Ubuntu's archive ships: 2.45.0, observed live in a cloud session
and 53 minor versions behind the two lanes that pin explicitly — the CI
runner image (ci-runner's Dockerfile, GH_VERSION 2.98.0) and local dev
machines (dotfiles, mise, 2.98.0). Scripts and hooks that shell out to
`gh` therefore ran against a different CLI depending on where they ran.

Install the same checksummed release asset the runner image does:
`gh_2.98.0_linux_amd64.tar.gz` from `github.com/cli/cli/releases`,
verified against the SHA-256 ci-runner pins, extracted to
`/usr/local/bin`, tarball removed. amd64-only for the same reason the
runner image is: the pinned hash covers that one asset. The step keeps
the script's best-effort contract — every failure path logs a WARN and
the build still exits 0.

The comment the apt install carried named `cli.github.com`, the upstream
apt repo, as the blocker. That is still true and still avoided: this
fetches a release asset from `github.com`, which is on the default
allowlist, not an apt repo.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPLatLkg4329L8eyfxhuMa
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 5, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-05T19:34:03.176294Z 7537a4d PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@claude

claude Bot commented Sep 5, 2026 •

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 3m 53s —— View job


Code review complete

  • Gather context (PR body, diff, files)
  • Review components/cloud-environment/setup.sh
  • Review components/cloud-environment/setup.test.sh
  • Review components/cloud-environment/README.md
  • Post findings

Summary

The change is well-scoped and does what the PR body says: gh now installs from the checksummed upstream release asset instead of apt, matching the ci-runner and dotfiles lanes. I traced the install logic, the arch guard, the checksum verification, the tarball extraction, and the cleanup paths, and diffed against origin/main to confirm the removed apt-get install -y gh path and the new test assertions line up with what's described.

One finding, posted inline on setup.sh:

  • Dead apt-get update -y left in Track A — this call existed to refresh apt indices for the apt-get install -y gh that used to follow it. That install is gone; the only remaining apt consumer in the track (PowerShell) already does its own apt-get update -y after adding the Microsoft repo, which is the one that actually matters. The leftover call is now a no-op network round-trip in a track with a ~5-minute total build budget, and its log lines (apt-get update ok / WARN apt-get update failed) no longer gate anything real.

Everything else checked out:

  • The version/hash pins (GH_VERSION, GH_SHA256, 64 hex chars) are well-formed and consistent between setup.sh and the new setup.test.sh assertions.
  • The arch guard (amd64-only, WARN-and-skip on other arches) and the checksum-then-extract-then-chmod chain preserve the script's existing "every step is best-effort, never fail-closed, always exit 0" contract.
  • GH_TARBALL is declared before the parallel subshells fork, so Track A's subshell sees it correctly; the double cleanup (inline + end-of-run sweep) is intentional and harmless.
  • README's new "The gh pin" section and update-lifecycle bullet accurately describe the lockstep constraint and the amd64/best-effort caveats.
  • No other reference to the old SCRIPT_VERSION elsewhere in the repo that needed updating.
  • PR body satisfies the repo's PR body contract (No related issue: ... plus all four sections).

I wasn't able to execute shellcheck, bash -n, or the shell test harness in this sandbox (Bash tool calls beyond simple read-only git commands require interactive approval, which isn't available in this lane) — verification of the claims in the PR body's "Verification" section is based on static reading, not re-execution.
· branch

Comment thread components/cloud-environment/setup.sh Outdated
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown

Claude has reviewed this PR 1 time. The lane skips further automatic reviews after 5; deleting this comment resets the count.

The gh install switched from apt to a pinned release tarball, so the
apt-get update -y that used to prep the archive indices for it has no
consumer left in Track A. PowerShell's install already runs its own
apt-get update -y after adding the Microsoft repo, which is the one
that actually needs to see fresh indices.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPLatLkg4329L8eyfxhuMa

Copy link
Copy Markdown
Contributor Author

Lane claim — ccr-session-babysit-loop-20260905

Claiming the babysit-loop lane on this PR at head 5649296946cb2a953544f797933d2229392fcbc9. Claim expires 2026-09-06T07:51:50Z UTC; released or re-stamped before then.

Pre-claim re-verification (live, not the stale 11h-old view):

  • head 5649296946cb2a953544f797933d2229392fcbc9, base d98e1f53, mergeable=true, mergeable_state=blocked
  • 43/43 check runs success; combined commit status ci-lanes = success; the single required context ci-status is green
  • 1 review thread, resolved and outdated (the dead apt-get update -y, removed in 5649296)
  • no foreign lane-claim, no foreign activity — last event on this PR was 2026-09-05T19:37:42Z, ~11h ago

Generated by Claude Code

Copy link
Copy Markdown
Contributor Author

Lane release — ccr-session-babysit-loop-20260905

Releasing the claim posted at #issuecomment-5557568512. Head unchanged at 5649296946cb2a953544f797933d2229392fcbc9 throughout. No merge, no push, no force-push, no thread resolution, no settings change.

Merge readiness: UNPROVEN, not blocked-by-gate

The gate never returned a verdict, so nothing was proven ready and there is no ready-to-execute handoff (reference/safety.md, the denied-check clause). Command attempted, verbatim:

bash "${CLAUDE_PLUGIN_ROOT}/bin/source-control-babysit-merge" melodic-software/standards#533 --allowed-owners melodic-software --self-logins kyle-sexton

It failed twice, for two independent environment reasons:

  1. With the session's own gh (2.45.0): Unknown JSON field: "closingIssuesReferences".
  2. With a locally-extracted gh 2.98.0 on PATH: HTTP 403: This GraphQL query is not enabled for this session — only the pinned set of PR-review operations is served.

Per the safety contract, mergeable_state, the check rollup, and live gh state are not substitute verdicts, so this PR is reported as readiness-unproven rather than merge-ready. No merge was attempted by any other route.

Why mergeable_state=blocked — observed state, not a readiness verdict

Everything the lane can normally clear is already clear: 43/43 check runs success, the single required context ci-status green, the one review thread resolved, no blocking label, mergeable=true, required_approving_review_count=0.

The differentiator is commit attribution:

Commit Author GitHub user Signature
7537a4dc Claude <ksextonloops@outlook.com> none — unattributed unverified (reason=no_user)
56492969 Claude <noreply@anthropic.com> claude verified

Org ruleset 17988999 sets require_extra_approval_for_unattributed_changes: true; ruleset 17989003 sets required_signatures. There are zero approving reviews (both reviews are COMMENTED).

Control check — every recently merged PR here (#525, #528, #530, #531, #532) merged with zero approving reviews and with every commit attributed to kyle-sexton and verified. So zero approvals is normal on this base and is not the hold; the unattributed, unverified 7537a4dc is.

Why the lane cannot clear it

Both remedies are outside this lane's authority:

  • Re-authoring and re-signing 7537a4dc requires a force-push, forbidden by a cross-tier invariant in every tier including autopilot.
  • Manufacturing the extra approval is not available either: GitHub does not permit self-approval, and the PR author is the lane identity.

Branch freshness was assessed and deliberately left alone: diverged, ahead 2 / behind 7, but strict_required_status_checks_policy: false on this base, so being behind is not the hold. Refreshing would re-run 43 lanes without touching the actual blocker.

Incidental: the pin in this PR re-validated independently

Downloaded gh_2.98.0_linux_amd64.tar.gz fresh in this session; sha256sum returns 3b8ac6b30336802fc1a858d7c084e11cdf24ac1a761ca90b68022d7d729208de, matching this PR's GH_SHA256 and ci-runner's ARG GH_SHA256 exactly, and the extracted binary reports gh version 2.98.0 (2026-08-20). The residual 403 risk the PR body flags for cli/cli release assets did not materialize from this fleet.

Operator action required

One of:

  1. Force-push fix/cloud-gh-version-parity with 7537a4dc re-authored to an attributed identity and signed with a key linked to that account, or
  2. Submit an approving review from a second identity to satisfy require_extra_approval_for_unattributed_changes.

Either clears the last hold; nothing else on this PR is outstanding.


Generated by Claude Code

Resolve the only conflict against main (SCRIPT_VERSION) so the pinned
upstream gh install lands on top of the fleet plugin list and later
setup.sh work, without regressing SCRIPT_VERSION behind 2026-09-06.2.

Co-authored-by: ksextonmelodic <ksextonmelodic@gmail.com>
@cursor

cursor Bot commented Sep 7, 2026

Copy link
Copy Markdown

Merged origin/main into this branch (c3c5509) to clear the conflict. The only conflict hunk was SCRIPT_VERSION; it is now 2026-09-07.1 so the stamp stays ahead of 2026-09-06.2 from #547.

The Claude review finding on the leftover apt-get update -y stays addressed (removed in 5649296, thread already resolved). Local re-check after the merge: setup.test.sh + cloud-bootstrap.test.sh pass, shellcheck --rcfile .shellcheckrc is clean on both scripts.

Waiting on ci-status then squash-merging.

kyle-sexton added a commit to melodic-software/claude-code-plugins that referenced this pull request Sep 7, 2026
…n floor (#3772)

No related issue: infrastructure parity found during PR-merge-lane
operation.

## Summary

The org runs `gh` in three lanes. Two pin it; one did not.

| Lane | Source | Version |
| --- | --- | --- |
| Local dev machines | `melodic-software/dotfiles` → mise | 2.98.0 |
| Self-hosted CI runner | `melodic-software/ci-runner` `Dockerfile` |
2.98.0 |
| Claude Code cloud sessions | standards
`components/cloud-environment/setup.sh`, `apt-get install -y gh` |
**2.45.0** |

A live cloud session on this fleet reports `gh version 2.45.0
(2025-07-18 Ubuntu
2.45.0-1ubuntu0.3)` — 53 minor versions behind the other two lanes. The
several
`source-control`, `guardrails`, and `work-items` suites that shell out
to `gh` were
running against a different CLI depending on where they ran.

The fix belongs upstream and is in

[melodic-software/standards#533](melodic-software/standards#533),
which replaces the apt install with the pinned, checksum-verified
`linux_amd64` release
tarball at 2.98.0. This PR is the docs half: this guide is where the
fleet's install method
and its verification checklist are written down, and both had gone
stale.

Worth naming the second-order finding: step 1 of the verification
checklist ran
`gh --version` and asserted nothing about the number it printed, so the
drift passed
verification every single time it was run. A presence check on a tool
whose *version* is
the thing under contract cannot detect the failure it exists to detect.

## Fix

Two edits to `docs/CLOUD-FLEET-SETUP.md`:

- **"What the canonical script does"** — `gh` is no longer described as
an apt install. It
now names the pinned, checksum-verified `linux_amd64` release tarball
from
`github.com/cli/cli`, at the same version and SHA-256 the CI runner
image and dotfiles'
mise pin carry, and says why (Ubuntu's archive `gh` is years stale).
PowerShell is still
  apt and still described that way.
- **Verification checklist, step 1** — `gh --version` gets a floor
instead of a presence
check: expect **2.98.0 or newer**, matching the other two lanes. The
step also says what a
`2.45.x` reading means (the pinned-tarball step failed and the session
fell back to
  Ubuntu's archive `gh`), and where to look for the cause
(`grep gh /var/log/melodic-env-setup.log` for the `WARN` line the
standards script logs on
every failure path). The rest of step 1 — `pwsh`, `dotnet --list-sdks`,
`node --version`,
  `check-tools` — is unchanged.

No behavior change here; the install itself lives in standards.

## Verification

- `scripts/affected-tests.sh --run docs/CLOUD-FLEET-SETUP.md`: the file
is a recorded
no-suite class in `scripts/affected-tests-no-suite.txt` (a non-shell CI
lane covers it);
  exit 0, no suites selected.
- `markdownlint-cli2 --config .markdownlint-cli2.jsonc
docs/CLOUD-FLEET-SETUP.md`: 0 issues.
- The 2.98.0 floor is not asserted from memory. Downloaded

`https://github.com/cli/cli/releases/download/v2.98.0/gh_2.98.0_linux_amd64.tar.gz`
from a
  live cloud session: `sha256sum` returns
`3b8ac6b30336802fc1a858d7c084e11cdf24ac1a761ca90b68022d7d729208de`,
matching `ci-runner`'s
`ARG GH_SHA256` exactly, and the extracted binary reports `gh version
2.98.0 (2026-08-20)`.
The same session's PATH `gh` reports 2.45.0 — the drift this documents,
observed directly
  rather than inferred.

Sequencing: the standards PR should merge first, and the environment
needs a cache rebuild
(any trivial edit to the setup-script field) before a session picks up
the new `gh`. Until
then a session will still read 2.45.0 and step 1 will correctly fail —
which is the point of
adding the floor.

## Related

-
[melodic-software/standards#533](melodic-software/standards#533)
  — the install change this documents.
- `melodic-software/ci-runner` `Dockerfile` — `ARG GH_VERSION` / `ARG
GH_SHA256`, the pinned
  pair both lanes now share.
- `melodic-software/dotfiles` `.chezmoidata/packages.yaml` and
`dot_config/mise/create_mise.lock` — the local-machine lane at the same
version.
-
[#2654](#2654)
— the
cloud-environment blockers whose network findings the standards change
reuses.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01VPLatLkg4329L8eyfxhuMa

---
_Generated by [Claude
Code](https://claude.ai/code/session_01VPLatLkg4329L8eyfxhuMa)_

---------

Co-authored-by: Claude <ksextonloops@outlook.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
@kyle-sexton
kyle-sexton merged commit 4b7d889 into main Sep 7, 2026
2 checks passed
@kyle-sexton
kyle-sexton deleted the fix/cloud-gh-version-parity branch September 7, 2026 08:45
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.

3 participants