Skip to content

Linux/WSL2 bootstrap keeps a newer native Claude Code: the pin is a floor (stacked on #159) - #177

Merged
seathatflowsinourveins merged 7 commits into
mainfrom
claude/linux-claude-code-floor-20260924
Sep 25, 2026
Merged

seathatflowsinourveins merged 7 commits into
mainfrom
claude/linux-claude-code-floor-20260924

Conversation

@seathatflowsinourveins

@seathatflowsinourveins seathatflowsinourveins commented Sep 24, 2026 •

Copy link
Copy Markdown
Owner

Stacked on #159

This branch is #159 merged onto current main (merge commit 939bff6), plus one commit of its own: 9e47304. Review only 9e47304. In the merge, the only textual conflict was two manifests/evidence.json hashes, for adoption/bootstrap.md and adoption/platforms/macos-arm64.md; both files auto-merged and were re-hashed.

Merge #159 first. This branch then rebases onto main as that one commit. Squash-merging this PR on its own would also land all of #159.

#159 also has a semantic conflict with main. #171 moved new machines to v2026.09.24.1, so test_pages_mentioning_a_changed_install_input_say_so now requires "changed after v2026.09.24.1". Merged onto current main, #159 fails that test with 6 errors, because its notes in adoption/bootstrap.md step 2 and adoption/platforms/macos-arm64.md name v2026.09.23.1. 9e47304 changes those notes to name v2026.09.24.1. The install inputs are byte-identical at both tags, so the notes stay true. #159 needs the same change when it merges main.

Why

adoption/bootstrap-linux.sh's install_native always ran "$bin" install <pin>. On a Linux/WSL2 host whose native updater (latest channel) is ahead of the pin, every bootstrap re-run moved Claude Code back to the pin and dropped the newer release's fixes. #159 fixed this downgrade on macOS only; its notes list "Linux can still downgrade" as out of scope.

What

  1. Linux install_native is now byte-identical to macOS's. It carries Adoption hygiene: native Claude Code pin is a floor (2.1.281), ai-memory assistant capture opt-in, ignore Claude Code local state #159's floor:
    • If ~/.local/bin/<bin> --version starts with a dotted numeric version at or above the pin, that launcher is kept. Versions are compared field by field as base-10 numbers, so 2.1.99 is older than 2.1.281.
    • A kept launcher means nothing is downloaded or installed. The script logs Kept installed claude-code <v>, and install_pin skips its Installed line.
    • A missing, older, non-numeric or failing launcher gets the unchanged install: fetch checks the SHA-256 before the binary runs.
    • A new test fails if the two scripts' copies of the function ever differ.
  2. adoption/pins-linux-x86_64.json: the claude-code install_note now describes the floor. It used to say the Linux script still reinstalls the pin every time.
  3. adoption/bootstrap.md step 2:
    • The stale sentence "Linux/WSL2's script and pins are the same at v2026.09.23 as on main" now says what actually differs at that tag, checked with git show v2026.09.23:…: the script installs the npm claude-code 2.1.278 pin and has no --configure-claude-user-profile step, and the pin file has no markitdown, tavily-cli, orx or agent-browser pin.
    • The claude-code note now names v2026.09.24.1 and covers both scripts.
  4. Platform pages: adoption/platforms/linux-wsl2.md step 1 gains the Linux "changed after v2026.09.24.1" note. The floor paragraph in adoption/platforms/macos-arm64.md now names v2026.09.24.1 and says Linux runs the same install_native.
  5. Evidence: manifests/evidence.json is re-hashed for every changed file with host_receipts.register_file, each path passed as a separate argument. manifests/evidence.json itself is left off that list. It has no entry for itself, and registering it would add an entry hashing its own pre-write bytes.

Change from the request: the task asked for a "changed after v2026.09.23.1" note. Since #171, the docs test requires the pinned tag, v2026.09.24.1, so the notes name that tag.

How verified (locally on macOS arm64, where /bin/bash is 3.2)

  • Validators: scripts/validate.py, scripts/validate_convergence.py --all-recorded --root . (18 records valid) and scripts/host_receipts.py validate all exit 0.
  • Other CI gates, run locally: release_due.py --strict-if-repinned origin/main, new_host_grand_list.py --check, component_matrix.py --check, build_ecosystem.py --check, validate_catalogs.py, validate_foundation.py and landscape.py all exit 0.
  • Linters: shellcheck -S style (0.11.0) on both bootstrap scripts and bash -n are clean.
  • Adoption test modules (all 7 test_adoption_* modules plus test_render_config): 341 tests, 35 skipped. There is 1 failure and 6 errors, none caused by this PR:
  • New NativeInstallFloorTests in tests/test_adoption_bootstrap.py: 6 tests, covering 5 kept launchers and 7 cases that must install.
    • fetch, install_native and install_pin are extracted unchanged from bootstrap-linux.sh and run against a one-pin fixture. Only curl is shimmed.
    • Where the host's sha256sum lacks GNU's --check --status (macOS), a sha256sum shim runs shasum -a 256 with fetch's own arguments, so the checksum is still really compared.
    • Also covered: a checksum mismatch still fails closed, and a kept launcher whose bin_dir is ~/.local/bin is not replaced by the exec wrapper.
  • Mutation checks:
    • Against origin/main's bootstrap-linux.sh, 7 checks fail: the 5 kept versions, the kept launcher in ~/.local/bin, and the shared-function check. The install-path tests pass on both versions, because that path is unchanged.
    • Deleting install_pin's early return for a kept launcher makes 6 checks fail.
  • Full python3 -m unittest, run locally inside the session sandbox: 3946 tests, 510 skipped, 1 failure and 36 errors. None come from this PR:
    • The 1 failure is the same LlamaWrapperQuotingTests failure described above.
    • 34 errors are PermissionError: [Errno 1] Operation not permitted from the sandbox: loopback binds, ps and paths. They are in test_offhost_app_state, test_order_throughput, test_research_workflow, test_adaptive_paper_metrics, test_native_maintenance (the application-delivery portability tests) and EmbedAcceptanceScriptTests. Outside the sandbox, those modules pass 158 of 158.
    • 2 are setUpClass errors in test_catalog_freshness_propose. The test commits a generated docs/ecosystem/index.html in a scratch repository, and this machine's global git pre-commit hook (gitleaks) refuses that commit. The same 2 errors occur on a clean origin/main tree (76 tests).

CI

  • 19 checks pass; 3 main-only jobs are skipped.
  • validate ran the full suite on Linux (3950 tests), where GNU sha256sum is used and no shim is needed. validate-macos gates on the adoption test modules (284 tests) and on the full suite on macos-15 (exit 0).
  • bootstrap-linux ran on a fresh ubuntu-24.04 runner with no ~/.local/bin/claude. It logged Installed claude-code 2.1.281 (native), and claude --version printed 2.1.281 (Claude Code). So the checksum-verified install still runs when no launcher exists (run 36007047862). bootstrap-macos and bootstrap-macos-brew show the same. A fresh runner never has a launcher to keep, so CI cannot exercise the kept path; the new tests cover it.

🤖 Generated with Claude Code

…ory assistant capture is opt-in, ignore Claude Code local state

bootstrap-macos.sh's install_native ran `"$bin" install <pin>` even when Claude Code's
native auto-updater had already installed something newer, so re-running the bootstrap
downgraded it. It now keeps a ~/.local/bin/<bin> whose --version is at or above the pin
(logs "Kept", downloads and installs nothing); a missing, older or unreadable launcher
takes the unchanged checksum-verified install. The check lives inside install_native
because tests/test_adoption_bootstrap.py runs that function on its own. Bash 3.2 safe.

Both claude-code pins move to 2.1.281 together (shared components keep one version on
both platforms). darwin-arm64 a922981f... comes from the official 2.1.281 manifest.json
and matches, byte for byte, the copy the native updater installed on a Mac. linux-x64
56fe3da8... comes from the same manifest and was not re-hashed; fetch() checks it before
running anything. 2.1.281 fixes a recursive rm of command-substitution output running
unprompted in auto and --dangerously-skip-permissions mode. The macOS page's pin table
and the generated new-host grand list follow the pins.

The Claude settings template no longer passes --capture-assistant to ai-memory's Stop
hook. render_config.py adds it only when AI_MEMORY_CAPTURE_ASSISTANT=true (host value
file or --set) and rejects any value other than true, false or empty. defaultMode and
skipDangerousModePermissionPrompt are unchanged.

.gitignore adds .claude/settings.local.json and .claude/worktrees/.

Not included, because open PRs #116, #145 and #151 own these files: the
manifests/evidence.json re-hash of the 12 changed files, and the adoption/bootstrap.md
"changed after `v2026.09.23.1`" marker for the three changed install inputs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Records the SHA-256/byte entries for every file this branch changes and
adds the "changed after `v2026.09.23.1`" note that the docs-consistency
test requires for the new install behavior (pins 2.1.281, floor instead
of unconditional reinstall). Both files are also edited by open PRs;
whichever lands later re-runs the same host_receipts.register_file helper.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolves the manifests/evidence.json conflict with #161 by regenerating the
adoption/bootstrap.md entry through scripts/host_receipts.py register_file().

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Stack the Linux Claude Code floor on #159's macOS floor. Only textual
conflict: the manifests/evidence.json hashes of adoption/bootstrap.md and
adoption/platforms/macos-arm64.md, both auto-merged; re-hashed with
host_receipts.register_file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…re too

adoption/bootstrap-linux.sh's install_native ran `"$bin" install <pin>`
unconditionally, so on a Linux/WSL2 host whose native updater (latest channel)
was ahead of the pin, re-running the bootstrap moved Claude Code back to the
pin and dropped newer fixes. install_native is now the same function as
bootstrap-macos.sh's (#159): a ~/.local/bin/<bin> whose --version first word
is a dotted numeric version at or above the pin is kept (nothing downloaded
or installed; install_pin logs "Kept installed ..." instead of "Installed
..."); a missing, older, non-numeric or failing launcher takes the unchanged
checksum-verified install.

Tests: NativeInstallFloorTests in tests/test_adoption_bootstrap.py run the
verbatim fetch/install_native/install_pin against a one-pin fixture (curl
shimmed; a shasum-backed sha256sum shim only where GNU --check --status is
missing), plus an identity check that both scripts share one install_native.

Docs: the Linux claude-code install_note describes the floor; bootstrap.md
step 2's stale "Linux/WSL2's script and pins are the same at v2026.09.23 as
on main" now says what differs at that tag; the claude-code floor notes on
bootstrap.md, linux-wsl2.md and macos-arm64.md name the currently pinned
release v2026.09.24.1 (#171), which the docs-consistency test requires.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

seathatflowsinourveins added a commit that referenced this pull request Sep 24, 2026
…up (#195)

Measured on macos-m5pro-20260924 (Apple M5 Pro, 24 GB) on 2026-09-24:
- Excluding the gitleaks runaway, processes held 26.84 GiB of footprint
  plus 3.07 GiB wired on 24 GiB. There were 357.8 GiB of swap writes in
  4.81 days, and two Jetsam events (09:45 and 09:46 EDT) killed 405 and
  then 4,255 processes.
- Loading the RAG embedder (qwen3-embedding:4b, 4.09 GiB) leaves
  2,079 MB unused with Chrome closed.
- The Metal working-set limit is 17.76 GiB, below the 17.67 GiB of the
  27B Q4_K_M weights plus any cache.

Added:
- evidence/artifacts/host-upgrade-20260924/upgrade-evidence.json: the
  RAM gate, the replacement recommendation (64 GB, M5 Pro) with Apple
  Store prices read 2026-09-24, and the Mac role. The Mac is the
  cockpit and coordinator; heavy and always-on lanes run on the WSL2
  workstation. It supersedes the projected-64 GB MacBook item of
  host-upgrade-20260923.
- evidence/artifacts/host-upgrade-20260924/foundation-alignment.json:
  memory, RAG and foundation-layer state on the Mac, the saturation
  status, and the model candidates. It also records the user's
  2026-09-24 license decision and the open items for the replacement
  Mac.
- docs/next-host-stages.md: the macOS role row and upgrade row, and
  replacement-Mac pickup steps. The pickup steps skip bootstrap-macos
  launchd agents where the agent-ecosystem services exist, and add the
  RAM-fit matrix and SSH to the workstation.
- manifests/evidence.json: registrations and the updated doc hash.

No Mac global state was changed. It has one writer
(foundation-lane-setup, agent-ecosystem#28). This leaves the files of
#185, #159 and #177 alone.

Co-authored-by: seathatflowsinourveins <234074349+seathatflowsinourveins@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	.gitignore
#	adoption/bootstrap.md
#	adoption/pins-linux-x86_64.json
#	adoption/platforms/linux-wsl2.md
#	docs/new-host-grand-list.md
#	manifests/evidence.json
@seathatflowsinourveins
seathatflowsinourveins enabled auto-merge (squash) September 25, 2026 14:44
…tency)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@seathatflowsinourveins
seathatflowsinourveins merged commit b792d6e into main Sep 25, 2026
23 checks passed
@seathatflowsinourveins
seathatflowsinourveins deleted the claude/linux-claude-code-floor-20260924 branch September 25, 2026 15:05
seathatflowsinourveins pushed a commit that referenced this pull request Sep 25, 2026
…0260925

Two use-stage receipts, native_proven, recorded with scripts/host_receipts.py
at origin/main 720e294 on the WSL2 Threadripper/RTX 4090 host from #231,
each bound to its layer winner's current pin:

- socraticode 1.14.0 (foundation/semantic-rag): through mcporter, the health
  check reaches Qdrant and the vLLM-served Nemotron embedder, the index of a
  separate clone of this repository is green, a gate-ladder question returns
  scripts/trading_gates.py and catalogs/us-equities/gates-20260922.json in the
  top eight, and an unrelated control query returns neither. The -2
  generation supersedes the first recording: it drops an unestablished
  "read-only clone" wording and asserts the launched package version against
  the winner pin.
- ai-memory 2.3.2 (foundation/durable-memory): read-only status of the host
  service (counts and flags only), then write, search, read-back and delete
  of a scoped probe page on a disposable scratch server with its own data
  directory; after deletion the same search finds nothing and read-page
  fails. The scratch server and directory are removed.

Rebased onto b792d6e (#177) with the docs/lanes.md hot-file protocol: main's
manifests/evidence.json plus the three receipts re-registered; the component
evidence matrix is regenerated with main's component_matrix.py, and the
new-host grand list is unchanged by these receipts.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
seathatflowsinourveins pushed a commit that referenced this pull request Sep 25, 2026
…obed

install_pin now records a pin in installed_pin_ids before #177's early
return for a kept native launcher, so a Claude Code at or above its 2.1.281
floor is still checked by the version report (minimum match). Pins re-taken
from main (2.1.281) with the version_probe declarations re-applied; the
linux-wsl2 step keeps both changed-after notes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
seathatflowsinourveins added a commit that referenced this pull request Oct 12, 2026
…e scanned snapshot (#1027 P1)

Astra's P1 at d6be1b0 (CHANGES_REQUESTED, reproduced on this PR's own EndToEndTests harness): nas_land.sh and uet_land.sh checked that
SQUASH_BODY_FILE was a readable, non-empty regular file and nothing more, so a body holding a home path, an account name, an address that
is not a no-reply one or a token shape went into main's permanent history through gh pr merge --body-file (her four synthetic payloads
each gave exit 0 and one merge call). gh reads the file when the merge call starts (cli/cli v2.102.0 pkg/cmd/pr/merge/merge.go:146-153;
:292-293 and http.go:72-75 send it as CommitBody; pkg/cmdutil/file_input.go:8-16), up to an hour or more after any check, so the scanned
bytes must also be the bytes gh reads.

When SQUASH_BODY_FILE is set, both scripts now, before the lock and every gh and git call:
- copy the file once to a snapshot (mktemp in TMPDIR, mode 0600; an EXIT trap removes it on every exit, SIGTERM and SIGHUP included);
- scan the snapshot with the new tools/landing/squash_body_scan.py (the repository's PRIVATE_CONTENT table from scripts/validate.py, a WSL
  UNC home path that table misses, an address that is not a no-reply one, and the names of the CC's landing-prohibited-names list; the
  list is parsed, never sourced) and with scripts/validate.py --scan-file (the scan the pre-push hook runs);
- stop with exit 2 and one fixed line on a match, a copy or scan that fails, a scanner or names file that is missing or unreadable, and any
  answer that is not a scan's own clean line (fail closed); nothing of the body, no path and no scanner output is printed;
- give gh the same snapshot, never the caller's file.
New variables: LANDING_BODY_SCAN, LANDING_SCANNER (default: scripts/validate.py of LANDING_NAS_REPO, which also fits the CC's live copies)
and LANDING_NAMES_FILE (default: the file under the coordination tree).

Three allowances, the CC's rulings of 2026-10-12, each with its tests:
- a names-list entry equal to the running host's own name (compared with hostname when the scan runs, never printed) is a warning count,
  not a refusal; every other name still refuses;
- a systemd unit instance, <name>@<instance>.service|timer|socket|target|path|mount (systemd/systemd man/systemd.unit.xml:121-135), is
  not an address;
- a short list of vendor co-author trailer addresses, each with the vendor source it comes from, passes: codex@openai.com, as the
  vendor's own repository carries it (openai/codex@db22c91, message line 38). #1002's email registry
  replaces the list. A real-provider address is still refused (the control).

Tests (22 new or changed; at d6be1b0 they give 140 failures and 5 errors, here they pass; the allowance tests also fail on the scan
without the allowance):
- a body with a home path of each form (Linux, macOS, Windows with either slash, the WSL mount, the WSL UNC forms with one or doubled
  backslashes, the dash-encoded and graph-id forms, percent-encoded), a name of a synthetic names file, an address that is not a no-reply
  one (with look-alikes) or a token shape (seven kinds) is refused by both scripts with exit 2 before any call;
- a missing or failing scanner, names file or validate.py, a scanner that prints nothing or another line, and every answer of
  validate.py --scan-file but its clean one refuse the landing;
- the valid-body control lands (exit 0, one merge call) with the body's bytes read from a mode-0600 snapshot, after both scans ran on it;
- a file rewritten after the scan does not reach the merge; the snapshot exists during the run and is gone after exits 0, 3 and 7 and
  after SIGTERM and SIGHUP; the defaults of the new variables; the real validate.py --scan-file where Betterleaks is installed;
- the allowances: a body naming the host lands with the count printed and the name not, and another listed name still refuses; unit
  instances land, their look-alikes and a provider address do not; the vendor address lands, another local part at the vendor's domain,
  the vendor's local part at another domain and a provider address do not;
- squash_body_scan.py by itself: the names file's formats and that no line of it runs, the address, unit and vendor rules, the host name,
  a name as a whole token, and its one output line.

Measured on the last 600 commit messages of main at 3a8a846 (counts only): 8 refused: 0 names, 5 addresses, 3 home paths; 26
messages name the host (a warning); 402 carry the vendor noreply trailer, which passes. main's history keeps 3 messages with a home path and
3 with an address at a mail provider; history is immutable, as for CC correction #177, and nothing here rewrites it.

The README records the scan, the variables, the exit-2 cases, the known gaps and the sources: cli/cli v2.102.0, MicrosoftDocs/WSL,
github/docs, systemd/systemd, openai/codex, the bash manual, CPython's -I and socket. The README digests and manifests/evidence.json
follow the changed files.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
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.

1 participant