Skip to content

feat(claude): bootstrap the pinned Node toolchain in cloud sessions - #390

Merged
kyle-sexton merged 1 commit into
mainfrom
claude/cloud-session-bootstrap
Aug 15, 2026
Merged

feat(claude): bootstrap the pinned Node toolchain in cloud sessions#390
kyle-sexton merged 1 commit into
mainfrom
claude/cloud-session-bootstrap

Conversation

@kyle-sexton

Copy link
Copy Markdown
Contributor

Summary

Cloud sessions (web, claude --cloud, mobile, routines) start on a fresh Ubuntu VM shipping Node 20/21/22 with no node_modules, so a session here runs the component contract tests and lint gates on the wrong Node against missing tooling. The fleet-wide verification run confirmed this class of failure (melodic-software/claude-code-plugins#2654).

This is this repository as a consumer, and is deliberately separate from #388, which adds the shared-environment component this repository owns. Same rollout, opposite side of the boundary: #388 is the org's generic warm cache, this is the exact pin only a hook can read.

No linked issue

Changes

  • .claude/hooks/cloud-bootstrap.sh (new) — Node from .node-version via the VM's nvm, then npm ci when the lockfile is newer than the installed tree, exported through $CLAUDE_ENV_FILE (dedup-guarded, since SessionStart fires on resume too).
  • .claude/settings.json — registers it ahead of the existing plugin-install hook.

Cloud-only (CLAUDE_CODE_REMOTE=true), idempotent, warn-and-continue. Local sessions are unaffected — the guard exits 0 before the hook does anything.

Verification

  • shellcheck -x -P SCRIPTDIR clean under this repo's .shellcheckrc
  • .claude/settings.json parses

Not claimed: I have not run this hook inside a live cloud session. Its logic is the fleet template, exercised by a 12-case suite in the medley sibling PR (melodic-software/medley#1818); the Node and npm cases there cover exactly the two blocks in this copy.

Scope note: only the root lockfile is synced. distribution/ has its own lockfile and is left to its existing npm ci --prefix distribution flow rather than being synced speculatively on every session.

Related

🤖 Generated with Claude Code

https://claude.ai/code/session_01AXaqhrFZeGTzbCJun12Ngx


Generated by Claude Code

@cursor

cursor Bot commented Aug 15, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e9750d32f4

ℹ️ 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .claude/hooks/cloud-bootstrap.sh Outdated
@claude

claude Bot commented Aug 15, 2026

Copy link
Copy Markdown

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


Claude finished the code review

  • Gather context (PR diff, CLAUDE.md, changed files)
  • Review .claude/hooks/cloud-bootstrap.sh
  • Review .claude/settings.json
  • Post inline findings (if any)
  • Post final summary

Summary

Reviewed the new .claude/hooks/cloud-bootstrap.sh SessionStart hook and the .claude/settings.json registration. No correctness or maintainability issues found — no inline comments posted.

Notes from the walkthrough:

  • Guard/idempotency logic is sound. The CLAUDE_CODE_REMOTE=true early-exit (L18) correctly no-ops local sessions, and env_line's grep -qxF dedup (L26-L28) correctly handles repeated SessionStart firing on resume.
  • Version-pin comparison is correct for this repo's .node-version (24.18.0, no v prefix), so "$(node --version)" != "v$node_pin" (L32) compares correctly.
  • nvm install failure path degrades gracefully — it warns and falls through to whatever node is currently on PATH, and the subsequent command -v node / env_line block (L48-L52) still exports a valid (if unpinned) PATH, consistent with the "warn-and-continue" design stated in the file's header comment.
  • npm ci staleness check (node_modules/.package-lock.json missing or older than package-lock.json, L59-L61) is a standard, correct idiom for skipping reinstalls when the tree is already in sync — first cloud run (no node_modules) always triggers it, later resumes correctly skip it once npm ci regenerates node_modules/.package-lock.json with a newer mtime.
  • .claude/settings.json change is a minimal, well-formed addition — the new hook entry is placed ahead of install-plugins.sh in the startup|resume array as described, JSON structure and existing entries are untouched.

Consistent with the PR's own disclosure: the CLAUDE_CODE_REMOTE env var contract and live cloud behavior aren't independently verifiable from this static review — that's appropriately covered by the sibling test suite referenced in the PR description rather than something this review can confirm. I wasn't able to independently execute shellcheck or a JSON validator in this sandboxed environment, but manual review of both files found no syntax or logic defects, consistent with the verification the PR author already reported.

No changes requested.

@github-actions

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.

@kyle-sexton
kyle-sexton force-pushed the claude/cloud-session-bootstrap branch from 40e91ab to 0959182 Compare August 15, 2026 06:56
Rebased onto the merged `.claude/cloud-bootstrap.sh` layout. The earlier
version of this branch added a second script at
`.claude/hooks/cloud-bootstrap.sh` with its own SessionStart registration,
which duplicates what main already has.

Main's bootstrap installs the plugin catalog and nothing else, so a cloud
session still runs the component contract tests and lint gates on whatever
Node the VM image ships, against missing node_modules
(melodic-software/claude-code-plugins#2654).

Inserted ahead of the plugin-CLI guards on purpose: those `command -v` checks
exit 0 when `claude` or `jq` is missing, and the toolchain must not be
collateral damage of an unrelated CLI being absent. Additive — 76 lines added,
none removed.

- Node from `.node-version` via the VM's nvm, exported through
  `$CLAUDE_ENV_FILE` (dedup-guarded, since SessionStart fires on resume).
- `npm ci` per project, not just the root. The root package.json declares no
  workspaces, so a root install never reaches the independently-locked
  projects under components/ (concurrency-policy, dependabot-policy,
  pr-convention-policy, runner-policy) or distribution/ — their modules would
  be missing and `npm run test:runner-policy` would fail with
  ERR_MODULE_NOT_FOUND despite the bootstrap reporting success. ci.yml already
  works around this with its own `npm ci --prefix` steps; npm_ci_at() does the
  same while keeping the per-project freshness check, so a resumed session
  skips every tree already in sync.

The subshell sets its own `set +e` and ends in an explicit `exit 0` rather
than being wrapped in `|| true`: this file runs under `set -e`, and wrapping
would put every call inside the subshell in an `||` context, which is exactly
what .shellcheckrc's check-set-e-suppressed (SC2310) exists to flag.

Verified: shfmt -d clean, shellcheck -x -P SCRIPTDIR clean under the repo
rcfile, bash -n parses.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXaqhrFZeGTzbCJun12Ngx
@kyle-sexton
kyle-sexton force-pushed the claude/cloud-session-bootstrap branch from 0959182 to e3d2952 Compare August 15, 2026 06:58
@kyle-sexton
kyle-sexton merged commit 55e25d6 into main Aug 15, 2026
45 checks passed
@kyle-sexton
kyle-sexton deleted the claude/cloud-session-bootstrap branch August 15, 2026 07:13
kyle-sexton added a commit that referenced this pull request Aug 16, 2026
## Summary

Collapses the fleet's twelve hand-copied, drifted
`.claude/cloud-bootstrap.sh` generations into one standards-owned
component, distributed by exact materialization with the standard
tri-mode lever: take (`managed`), enrich (a repo's own never-synced
`.claude/cloud-bootstrap.local.sh`, run by the canonical script), or
customize (`locally-owned`).

## Fix

- **New component `components/cloud-bootstrap/`** — the canonical script
is generic by construction: no repo names, marketplace identifiers, or
pinned versions. Node from `.node-version`, `npm ci` from the root
lockfile, .NET exactly as `global.json` pins, marketplaces and plugins
from `.claude/settings.json` (every declared marketplace; every
`enabledPlugins: true` entry, whichever marketplace it names — which
also removes the previous hardcoded-marketplace second code path). Adds
an environment-snapshot stamp log line (per-account build visibility)
and shallow-clone repair (`origin/main` resolvable for base-ref diffs).
- **`distribution/sync-manifest.yml`** — `managed` for `.github`,
`ci-runner`, `ci-workflows`, `github-iac`, `provisioning`;
`locally-owned` with recorded reasons for `claude-code-plugins`
(directory-source dogfooding) and `medley` (repo-specific toolchain;
converge later); `dotfiles` excluded pending its cloud adoption.
- **Contract tests** — `cloud-bootstrap.test.sh` (parse,
generic-by-construction, calling-contract landmarks, stamp-path lockstep
with cloud-environment, byte-equality with this repo's own materialized
copy); `setup.test.sh` now lockstep-tests the cloud-environment Node
warm-cache pin against `.node-version`.
- **`distribution/check-plugin-baseline.sh`** — report-only fleet drift
check of each target's plugin catalog against this repository's own
settings file (the dogfooded fleet baseline), with an offline
`--compare` mode under test.
- CI: the cloud-environment job now runs both cloud contract tests.

## Verification

- `harness/shell/run-tests.sh` over the three touched test files: 3
passed.
- `distribution/sync-manifest.sh validate`: `Manifest valid: 38
components, 8 targets`; `plan --targets melodic-software/ci-runner`
renders `100755 components/cloud-bootstrap/cloud-bootstrap.sh ->
.claude/cloud-bootstrap.sh`.
- shellcheck + shfmt clean on all new/changed shell; markdownlint clean
on changed docs; lefthook pre-commit green.
- `sync-manifest.test.sh`: engine fixture cases pass except the two
symlink-rejection cases, which fail only on Windows (symlink creation
degrades to a regular file there); they are fixture-local and unaffected
by this change — CI's Ubuntu run is authoritative.
- Post-merge follow-ups (deliberate, not in this PR): the provisioning
fan-out PR needs its repo-specific toolchain re-added as
`.claude/cloud-bootstrap.local.sh` (extraction prepared); a live cloud
verification per the cloud-environment README's stamp checklist.

No linked issue

## Related

- Builds on #388/#389/#390 (canonical environment script; bootstrap
split; Node toolchain).
- Node-pin lockstep follows the #394 precedent for hand-duplicated-value
drift.

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

https://claude.ai/code/session_012tKxg98B3QySqEPf8UqwX3

---------

Co-authored-by: Claude Fable 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