Skip to content
45 changes: 41 additions & 4 deletions adoption/bootstrap-linux.sh
Original file line number Diff line number Diff line change
Expand Up @@ -295,14 +295,47 @@ install_npm() {
# lines 158-171): fetch the exact per-version download, chmod it executable,
# run `"$bin" install <version>`, and leave the binary's own auto-update in
# control from there (no DISABLE_AUTOUPDATER opt-out).
#
# The pin is a floor, not a ceiling (2026-09-24): `"$bin" install <pin>`
# moves the installer's own launcher back to the pin, dropping a newer
# auto-updated release's fixes. A launcher at $HOME/.local/bin/<bin> whose
# `--version` first word ("2.1.281" of "2.1.281 (Claude Code)") is a dotted
# numeric version at or above the pin is kept: nothing is downloaded or
# installed, and install_pin logs "Kept" instead of "Installed". Fields are
# compared as base-10 numbers (2.1.99 is older than 2.1.281). No launcher, a
# failing --version, a non-numeric version or an older one takes the
# unchanged checksum-verified install. The function is identical to
# adoption/bootstrap-macos.sh's install_native, which added this floor first;
# tests/test_adoption_bootstrap.py fails if the two copies differ.
install_native() {
# $5 is the command the native installer creates (the pin's `bin`, e.g.
# claude-code installs ~/.local/bin/claude); it defaults to the pin id.
local id="$1" version="$2" url="$3" sha256="$4" bin_name="${5:-$1}"
local download="$cache_dir/${id}-${version}-native"
fetch "$url" "$sha256" "$download"
chmod 0755 "$download"
"$download" install "$version"
local launcher="$HOME/.local/bin/$bin_name" installed="" keep=0 have want have_field want_field
if [[ -x "$launcher" ]] && installed="$("$launcher" --version </dev/null 2>/dev/null)"; then
installed="${installed%%[[:space:]]*}"
keep=1
case "$installed" in ''|*[!0-9.]*|.*|*.|*..*) keep=0 ;; esac
case "$version" in ''|*[!0-9.]*|.*|*.|*..*) keep=0 ;; esac
have="$installed" want="$version"
while [[ "$keep" == 1 && -n "$have$want" ]]; do
have_field="${have%%.*}" want_field="${want%%.*}"
if [[ "$have" == *.* ]]; then have="${have#*.}"; else have=""; fi
if [[ "$want" == *.* ]]; then want="${want#*.}"; else want=""; fi
if (( 10#${have_field:-0} > 10#${want_field:-0} )); then break; fi
if (( 10#${have_field:-0} < 10#${want_field:-0} )); then keep=0; fi
done
fi
if [[ "$keep" == 1 ]]; then
native_floor_kept="$installed"
printf 'Kept installed %s %s: at or above the pinned floor %s, so nothing was downloaded or installed (installing the pin would downgrade it).\n' \
"$id" "$installed" "$version"
else
local download="$cache_dir/${id}-${version}-native"
fetch "$url" "$sha256" "$download"
chmod 0755 "$download"
"$download" install "$version"
fi
# When bin_dir is the installer's own ~/.local/bin, its launcher (a symlink
# into ~/.local/share/claude/versions) already provides the command; writing
# ours there would replace it with a script that execs itself.
Expand Down Expand Up @@ -360,6 +393,9 @@ install_pin() {
printf 'Refusing to install %s %s: pin has no verified sha256 (%s)\n' "$id" "$version" "$note" >&2
exit 1
fi
# install_native sets this when it keeps an installed launcher at or above
# the pin (the pin is a floor); it has already logged that decision.
native_floor_kept=""
case "$id-$kind" in
node-tarball) install_node "$version" "$url" "$sha256" ;;
uv-tarball) install_uv "$version" "$url" "$sha256" ;;
Expand All @@ -370,6 +406,7 @@ install_pin() {
*-uv-tool) install_uv_tool "$id" "$version" ;;
*) printf 'Unknown pin kind %s for %s.\n' "$kind" "$id" >&2; exit 1 ;;
esac
[[ -z "$native_floor_kept" ]] || return 0
printf 'Installed %s %s (%s)\n' "$id" "$version" "$kind"
}

Expand Down
11 changes: 7 additions & 4 deletions adoption/bootstrap.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,13 @@ GitHub-hosted macOS runner; see
host. Versions, URLs and hashes are unchanged, so a host at that tag
installs the same artifacts.

The macOS script and both claude-code pins changed after `v2026.09.23.1`:
at that tag the pins are 2.1.280 and `adoption/bootstrap-macos.sh` reinstalls
the pin even over a newer Claude Code; on main the pins are 2.1.281 and the
script keeps an installed launcher at or above the pin.
Both scripts and both claude-code pins changed after `v2026.09.24.1`: at
that tag the pins are 2.1.280 and `adoption/bootstrap-linux.sh` and
`adoption/bootstrap-macos.sh` reinstall the pin even over a newer Claude
Code; on main the pins are 2.1.281 and both scripts keep an installed
`~/.local/bin/claude` at or above the pin (logging `Kept installed
claude-code <version>`), running the checksum-verified install only when
that launcher is missing, older or unreadable.

3. **Native sign-in.** Neither client's credentials transfer between machines
(`adoption/manifest.json` `policy.authentication_transfer: native_login_on_target_only`).
Expand Down
2 changes: 1 addition & 1 deletion adoption/pins-linux-x86_64.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"kind": "native",
"url": "https://downloads.claude.ai/claude-code-releases/2.1.281/linux-x64/claude",
"sha256": "56fe3da88458465fb27d7e9299dddb3fead55750fb9c2de795f233b5eea6dce1",
"install_note": "minimum version; native auto-update on the latest channel. Raised from 2.1.280 (1e08503dbdf3c2cb0d706d32f3408277388d1c76ef108673e8fe42c1b322925b, verified 2026-09-23 by downloading the linux-x64 binary and re-hashing it) in step with the macOS pin, since shared components keep one version on both platforms: 2.1.281 fixes a recursive rm of command-substitution output running unprompted in auto and --dangerously-skip-permissions mode. This sha256 is the 2.1.281 manifest.json's platforms.linux-x64.checksum (fetched 2026-09-24, size 237375560); the linux-x64 binary was not re-downloaded or re-hashed for it, and fetch() verifies the served bytes against it before anything runs. Superseded the prior npm-wrapper pin (2.1.278, DISABLE_AUTOUPDATER=1) once the native installer became the reference path: fetch the binary, verify its sha256, then run `\"$bin\" install 2.1.281`, matching ~/codex-ecosystem/bin/bootstrap-linux.sh's own claude-code step. The installed binary self-manages ~/.local/share/claude/versions and the ~/.local/bin/claude launcher and auto-updates on the latest channel from there; this pin is a floor, not a ceiling, and no DISABLE_AUTOUPDATER opt-out is set. adoption/bootstrap-linux.sh still runs that install unconditionally (only bootstrap-macos.sh keeps an installed launcher at or above the pin), so a re-run there moves a newer install back to this pin.",
"install_note": "minimum version; native auto-update on the latest channel. Raised from 2.1.280 (1e08503dbdf3c2cb0d706d32f3408277388d1c76ef108673e8fe42c1b322925b, verified 2026-09-23 by downloading the linux-x64 binary and re-hashing it) in step with the macOS pin, since shared components keep one version on both platforms: 2.1.281 fixes a recursive rm of command-substitution output running unprompted in auto and --dangerously-skip-permissions mode. This sha256 is the 2.1.281 manifest.json's platforms.linux-x64.checksum (fetched 2026-09-24, size 237375560); the linux-x64 binary was not re-downloaded or re-hashed for it, and fetch() verifies the served bytes against it before anything runs. Superseded the prior npm-wrapper pin (2.1.278, DISABLE_AUTOUPDATER=1) once the native installer became the reference path: fetch the binary, verify its sha256, then run `\"$bin\" install 2.1.281`, matching ~/codex-ecosystem/bin/bootstrap-linux.sh's own claude-code step. The installed binary self-manages ~/.local/share/claude/versions and the ~/.local/bin/claude launcher and auto-updates on the latest channel from there; this pin is a floor, not a ceiling: adoption/bootstrap-linux.sh's install_native (identical to bootstrap-macos.sh's since 2026-09-24) keeps a ~/.local/bin/claude whose --version already reports 2.1.281 or newer, logging `Kept installed claude-code <version>` with nothing downloaded or installed, and runs the checksum-verified install only when that launcher is missing, older or unreadable, so re-running the bootstrap never moves a newer auto-updated install back to this pin. No DISABLE_AUTOUPDATER opt-out is set.",
"bin": "claude"
},
{
Expand Down
8 changes: 8 additions & 0 deletions adoption/platforms/linux-wsl2.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,14 @@ itself evidence the WSL UVA gap closed.

1. Follow [`adoption/bootstrap.md`](../bootstrap.md) steps 1–3 (prerequisites,
`bootstrap-linux.sh --profile <id>`, native sign-in).
`adoption/bootstrap-linux.sh` and its `claude-code` pin
changed after `v2026.09.24.1`: at that tag the pin is 2.1.280 and the
script reinstalls it even over a newer Claude Code, so a re-run
downgrades a native auto-updated install. On main the pin is 2.1.281 and a floor: the script
keeps a `~/.local/bin/claude` whose `--version` reports 2.1.281 or newer
(logging `Kept installed claude-code <version>`, with nothing downloaded
or installed) and runs the checksum-verified install only when that
launcher is missing, older or unreadable.
2. Recreate the SDK only for the `research-runtime` profile using
[`adoption/sdk/README.md`](../sdk/README.md)'s transitive lock; retain the
same exact-match and uncached-reinstall checks as
Expand Down
5 changes: 3 additions & 2 deletions adoption/platforms/macos-arm64.md
Original file line number Diff line number Diff line change
Expand Up @@ -255,10 +255,11 @@ The pin is a floor: when `~/.local/bin/claude --version` already reports the
pinned version or newer, `install_native` keeps that launcher, downloads and
installs nothing, and logs `Kept installed claude-code <version>`; only a
missing, older or unreadable launcher gets the verified install, so re-running
the bootstrap never moves a native auto-updated Claude Code back to the pin.
the bootstrap never moves a native auto-updated Claude Code back to the pin
(`adoption/bootstrap-linux.sh` runs the same `install_native`).
The script and both claude-code pins (2.1.281, which fixes a recursive `rm` of
command-substitution output running unprompted in auto and bypass mode)
changed after `v2026.09.23.1`: at that tag the pins are 2.1.280 and the script
changed after `v2026.09.24.1`: at that tag the pins are 2.1.280 and the script
runs the pinned install unconditionally, downgrading a newer Claude Code.

`llama-server` is a profile `required_command`, so llama.cpp is pinned rather
Expand Down
24 changes: 12 additions & 12 deletions manifests/evidence.json
Original file line number Diff line number Diff line change
Expand Up @@ -3230,8 +3230,8 @@
},
{
"path": "adoption/bootstrap-linux.sh",
"sha256": "7ffa94c2a654c2ceb2fe0ab56ac6bc7cce8e44b135ac10f824ce257ac2b294ae",
"bytes": 18084
"sha256": "5f92b15675a5476e672786b95ea33b81f1085d0d49e20ffc059e0531008b0957",
"bytes": 20202
},
{
"path": "adoption/bootstrap-macos.sh",
Expand All @@ -3240,8 +3240,8 @@
},
{
"path": "adoption/bootstrap.md",
"sha256": "24fcfdda13703b24deec5e5f08c2d9e6099cc49bea5ad1798cd0397ad733cafc",
"bytes": 26397
"sha256": "49fd87607692e9594d8d9bf30651d132b163f7c6f74c4f23ebf896bb212919b3",
"bytes": 26593
},
{
"path": "adoption/credential-inventory.json",
Expand Down Expand Up @@ -3330,8 +3330,8 @@
},
{
"path": "adoption/pins-linux-x86_64.json",
"sha256": "7f1309c43f303a9a950c1531ee557c9ac24140273223aada236e03cd8662b705",
"bytes": 12499
"sha256": "c4d467fd12afc72e21310852d4fb5ee2415c40248adfab64f26bb9f37b9156b8",
"bytes": 12738
},
{
"path": "adoption/pins-macos-arm64.json",
Expand All @@ -3340,13 +3340,13 @@
},
{
"path": "adoption/platforms/linux-wsl2.md",
"sha256": "fa6f8af6476d9aef58fb34fdaa485dffce2b662e745f3302543643078e11bd17",
"bytes": 10868
"sha256": "6d5112af87f2f7376300de2242171ce9ace10afc5b33db270fba3c020a207323",
"bytes": 11431
},
{
"path": "adoption/platforms/macos-arm64.md",
"sha256": "7e7c13d355287e80cbb8fb9d44aad9fd1c56493713f31a781c0a8f093ce5bd7b",
"bytes": 38916
"sha256": "10afaf6bcc72e8bf68251f5f0a4916108d7ae18ae890a867d52849546150aa7d",
"bytes": 38979
},
{
"path": "adoption/pre-login-observation.json",
Expand Down Expand Up @@ -29636,8 +29636,8 @@
},
{
"path": "tests/test_adoption_bootstrap.py",
"sha256": "c1a74467b813530f08be6ae0fa22625cd4918c91e3e2672b91f46c7be0d226d6",
"bytes": 26131
"sha256": "79f3e2f17307bf0cc5e6e1da3f8c13874a10a78f6c15857691ed029632941733",
"bytes": 35461
},
{
"path": "tests/test_adoption_bootstrap_macos.py",
Expand Down
Loading
Loading