feat: provision Claude Code cloud sessions from source control - #1763
Conversation
Cloud environments are account-scoped, not per-repository, so everything repo-specific moves into source control: .claude/settings.json registers a SessionStart hook (startup|resume) and .claude/hooks/session-start.sh provisions a fresh cloud VM to the same tool inventory ci.yml pins — Node from .node-version, the claude CLI and Biome via root npm ci, ruff from the hash-locked requirements file, and best-effort hygiene binaries (shellcheck, actionlint, typos, editorconfig-checker, gitleaks, markdownlint-cli2, check-jsonschema) plus full git history for the base-ref diff gates. Local sessions exit immediately via the CLAUDE_CODE_REMOTE guard. docs/CLOUD-SESSIONS.md records the research: environment scope, the setup-script vs SessionStart split, recommended environment dialog settings, and the standards-sync caveat for .claude/settings.json. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SxpZePLupZiV8E5bwFbmBS
…c claims Reshape docs/CLOUD-SESSIONS.md into three parts — what cloud sessions and environments are, how to set them up for any account or repo (pointers to the official pages instead of restated details, to avoid drift), and how this repository is set up. Correct an over-inferred claim: per the sync-commit history, .claude/settings.json and .github/requirements-ci.txt are repo-owned, not materialized from melodic-software/standards; of the files the hook reads, only .node-version is in the synced set. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SxpZePLupZiV8E5bwFbmBS
…sions Declare this repo as its own plugin marketplace (extraKnownMarketplaces, directory source with a relative path) and enable a lean default set in enabledPlugins: the six format/lint-on-edit hooks (markdown-format, bash-format, biome-format, typos-format, actionlint, eol-normalizer) plus guardrails, source-control, and skill-quality. Cloud sessions install these at session start from the clone; local collaborators are prompted on folder trust. Add shfmt (pinned, best-effort) to the session-start hook so the bash-format plugin's format pass has its tool, and document the plugin set and the GitHub-MCP-vs-gh-CLI split in docs/CLOUD-SESSIONS.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SxpZePLupZiV8E5bwFbmBS
…ent strategy Codify where setup belongs — cached VM setup script vs per-session SessionStart hook — with the caching cost model, the guarded setup-script line that bakes the repo bootstrap into the environment snapshot, and one-vs-several environment guidance (e.g. a future .NET repo). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SxpZePLupZiV8E5bwFbmBS
|
Claude finished @kyle-sexton's task in 3m 6s —— View job PR Review: feat: provision Claude Code cloud sessions from source controlTodo list:
Overall this is a well-thought-out, carefully layered addition. The architecture is sound (account-scoped environment stays generic, all repo-specific provisioning stays in source control), the idempotency design is good, the required/best-effort split is well-reasoned, and the documentation is thorough without duplicating upstream content. A couple of correctness and security items worth addressing:
|
|
Claude finished @kyle-sexton's task in 3m 48s —— View job Security Review
Two findings. No injection surfaces, no credential exposure, no dangerous GitHub Actions patterns. The issues are both in binary supply-chain trust. IMPORTANT — Binary tool downloads execute without integrity verificationConfidence: HIGH
curl -fsSL "$url" | tar -xz -C "$tmp" || ok=0It then checks only that the expected file path exists in the extracted tree before installing it with curl -fsSL -o "$bin_dir/shfmt" \
"https://github.com/mvdan/sh/releases/download/${shfmt_pin}/shfmt_${shfmt_pin}_linux_amd64"
chmod 0755 "$bin_dir/shfmt"This is notably inconsistent: Exploitation scenario: If any of the six upstream GitHub release pages serves a tampered binary (supply-chain compromise of those repos' release infrastructure), or if a MITM occurs between the cloud VM and GitHub's release CDN, a malicious binary lands in Blast radius: Limited to a single ephemeral cloud session (no persistent storage, no repo write access beyond what the GitHub proxy grants). However, a compromised Recommendation: Add SHA256 verification to # in VERSION PINS block
shellcheck_sha256="<hash>"
# in fetch_release_tool or inline
echo "$expected_sha256 $bin_dir/$name" | sha256sum --check --quiet || {
rm -f "$bin_dir/$name"
echo "session-start: warning: $name checksum mismatch; not installed" >&2
return 0
}SUGGESTION —
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 67ee12f7f9
ℹ️ 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".
Address all four reviewer findings on #1763: - Verify a pinned SHA-256 for every GitHub-release asset before install (shellcheck, actionlint, typos, editorconfig-checker, gitleaks, shfmt); a mismatch refuses the install with a visible warning and the session continues. shfmt now flows through the same fetch_release_tool path as a bare-binary asset. - Deduplicate the CLAUDE_ENV_FILE PATH append so resume re-runs cannot stack duplicate lines (exact-line grep guard). - Fetch origin/main with an explicit destination refspec so the ref materializes in single-branch clones where a bare fetch only writes FETCH_HEAD. - Pin check-jsonschema, the one previously unpinned install. Also rename the inverted ok flag to failed for shell-idiomatic reading. Verified: fresh-download run installs all six tools at pinned versions through the checksum path; a corrupted pin refuses install and exits 0; the env file holds exactly one PATH line after two runs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SxpZePLupZiV8E5bwFbmBS
|
All four reviewer findings addressed in 612510d:
Also renamed the inverted Generated by Claude Code |
|
Claude finished @kyle-sexton's task in 3m 25s —— View job PR Review: feat: provision Claude Code cloud sessions from source controlTodo list:
This review covers the current HEAD ( All four prior findings: correctly fixedSHA-256 verification (
New observations (no blockers)
npm install -g --no-audit --no-fund "markdownlint-cli2@${markdownlint_pin}" || ...Unlike the six GitHub-release tools (which now verify SHA-256) and SHA hash values are author-attested, not derivable from the script — The PR description says hashes were recorded from verified downloads. There's no
OverallThe four prior findings are all correctly fixed. The implementation is sound: |
|
Claude finished @kyle-sexton's task in 3m 57s —— View job Security ReviewTodo list:
The two IMPORTANT findings from the initial security review have been correctly addressed in
No CRITICAL or IMPORTANT findings on the updated code. Two low-severity items follow. SUGGESTION —
|
…#1773) No linked issue ## Summary Every first-attempt `pr-issue-linkage` CI failure in this repo is an authoring-time knowledge gap: the agent writing the PR body has no way to know the contract until CI rejects it one full round trip later (#1766 showed the pattern today — red at creation, green after a body edit). This PR makes the contract known and enforced at authoring time, on every surface a PR body can be written through, without weakening the required check itself. ## Fix Three layers, repo-level and plugin-level: - **CLAUDE.md** documents the body contract (closing keyword or literal `No linked issue`, plus a non-empty `## Related` section, validated after HTML-comment stripping) so agents write a passing body on the first try. - **Checked-in MCP gate** (`.claude/hooks/pr-linkage-mcp-gate.sh`, wired in `.claude/settings.json`): a PreToolUse hook that blocks a GitHub-MCP `create_pull_request`/`update_pull_request` whose body would fail the check — the surface cloud sessions use — with the exact missing lines named in the block message. Loads in any session that opens this repo, no plugin required. - **source-control plugin 0.41.0**: the same gate ships as `pr-linkage-mcp-gate` for every marketplace consumer, sibling to the existing `pr-body-linkage-gate` (`gh` surface); kill switch `pr_linkage_mcp_gate_enabled` (default true). The validator core (comment stripping, keyword/`## Related` judging, verdict wording) is extracted to one sourced lib, `pr-linkage-validator.sh`, consumed by both plugin hooks and the checked-in repo hook, so a drift fix against the upstream ci-workflows validator lands on every surface atomically. The merge with `main` also hand-reconciles `.claude/settings.json` where git's textual merge of this branch and #1763 produced duplicate `hooks`/`extraKnownMarketplaces`/`enabledPlugins` keys (JSON last-wins would have silently dropped the SessionStart hook and the 9-plugin roster): the file now carries both hooks and #1763's marketplace source and plugin set. ## Verification - `plugins/source-control/hooks/pr-linkage-mcp-gate.test.sh`: 17/17 (validator shapes, create-without-body blocks, update-without-body allows, owner/repo scope guard, kill switch). - `plugins/source-control/hooks/pr-body-linkage-gate.test.sh`: 135/135 — sibling regression across the shared-lib extraction. - `.claude/hooks/pr-linkage-mcp-gate.test.sh`: 16/16 against the checked-in hook sourcing the lib. - `shellcheck` (repo rcfile semantics) and `shfmt` clean on all four shell files; `check-changelog-parity.sh --check-bump origin/main`, `sync-hook-utils.sh --check`, and `check-manifest-duplicate-keys.py` all pass. - Hook contracts (PreToolUse MCP matchers, plugin `hooks/hooks.json`, `userConfig` → `CLAUDE_PLUGIN_OPTION_*` mapping) verified against the current hooks and plugins-reference docs fetched this session, per the fresh-docs mandate. ## Related - Refs #1763 — the cloud-session provisioning PR whose `.claude/settings.json` this branch merges with and reconciles. - Refs #1766 — today's observed instance of the first-attempt `pr-issue-linkage` failure this PR prevents. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- _Generated by [Claude Code](https://claude.ai/code/session_01KJdyaSTPhgkjYwJnVnhdV4)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
…session-start hook (#1772) No linked issue ## Summary Comment-only follow-up closing out the two informational notes the automated reviews left on #1763 after its blocking findings were fixed, so they're recorded in the code instead of a merged PR thread. ## Fix - Document the hash-bump procedure alongside the `VERSION PINS` block in `.claude/hooks/session-start.sh`: download the new asset, confirm the binary reports the pinned version, record `sha256sum <downloaded-asset>`. - State explicitly that the npm and pip/uv installs (markdownlint-cli2, check-jsonschema) rely on registry-level integrity metadata — an accepted, weaker trust anchor than the committed SHA-256s, since `npm install -g` has no `--require-hashes` equivalent. - The third low-severity review note (NVM_DIR path validation) is deliberately declined, with the reasoning in the commit message: forcing `/opt/nvm` risks breaking session start if the cloud image legitimately relocates nvm, against a threat that already requires environment control. ## Verification Comment-only diff (7 insertions, 1 deletion, one file). shfmt, shellcheck (repo rcfile), typos, and editorconfig-checker all pass on the changed file; hook behavior unchanged. ## Related Refs #1763 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01SxpZePLupZiV8E5bwFbmBS --- _Generated by [Claude Code](https://claude.ai/code/session_01SxpZePLupZiV8E5bwFbmBS)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
No linked issue
Summary
Cloud environments are account-scoped, not per-repository, so everything repo-specific for Claude Code cloud sessions moves into source control: a
SessionStartbootstrap hook, the repo declared as its own plugin marketplace with a curated default plugin set, and a research-backed how-to doc. The environment dialog stays generic (Default / Trusted / no variables / optionalghone-liner).Fix
.claude/hooks/session-start.sh— cloud-only bootstrap (CLAUDE_CODE_REMOTEguard) that provisions a fresh cloud VM to the same tool inventoryci.ymlpins, reading in-repo manifests where one exists: Node from.node-versionvia the VM's nvm, the pinnedclaudeCLI + Biome via rootnpm ci, ruff from the hash-locked.github/requirements-ci.txt, best-effort pinned hygiene binaries (shellcheck, actionlint, typos, editorconfig-checker, gitleaks, shfmt, markdownlint-cli2, check-jsonschema), full git history for the base-ref diff gates, and sessionPATHpersistence via$CLAUDE_ENV_FILE. Required steps fail the session start; best-effort steps warn and continue. Idempotent: ~40 s on a fresh VM, ~3 s on re-run..claude/settings.json— registers the hook (startup|resume), declares this repo as its own marketplace (extraKnownMarketplaces,directorysource with a relative path), and enables a lean default plugin set (enabledPlugins):markdown-format,bash-format,biome-format,typos-format,actionlint,eol-normalizer,guardrails,source-control,skill-quality.docs/CLOUD-SESSIONS.md— concepts → generic setup guide (pointers to official docs, no copied detail) → this repo's setup, including setup-script-vs-SessionStart decision criteria, the cache-the-hook performance lever, one-vs-several environment guidance, and the GitHub-MCP-vs-gh-CLI split.Verification
.claude/settings.jsonvalidates against the official claude-code-settings JSON Schema; all nine plugin names verified present in.claude-plugin/marketplace.json.check-shell-portability,check-skill-portability, gitleaks (origin/main..HEAD) — all pass.plugins/bash-format35/35,lib/hook-utils115/115.Related
N/A
🤖 Generated with Claude Code
https://claude.ai/code/session_01SxpZePLupZiV8E5bwFbmBS
Generated by Claude Code