feat(harness): cloud session boot + mandatory karpathy/btoo priming + shared volume#16
feat(harness): cloud session boot + mandatory karpathy/btoo priming + shared volume#16
Conversation
… cross-container shared volume
Three coordinated additions land in one shippable change:
1. Cloud session boot (scripts/setup.js)
- Idempotent orchestrator: materializes .env from .env.example, runs
yarn install, pre-warms MCP servers from .mcp.json, populates the
cross-container shared volume, runs npm run verify --skip-mcp.
- Exposed as both a host-side initializeCommand (--init-env-only flag)
and a container postCreate step.
- postCreate.sh shrinks from 59 to ~15 lines by delegating.
2. Mandatory standards priming (scripts/hooks/btoo-directives.js)
- New UserPromptSubmit hook injects /karpathy-guidelines (4 rules) and
the Boil-the-Ocean completeness standard (verbatim user phrasing) on
every turn as additionalContext.
- Profile-gated via ECC_HOOK_PROFILE / ECC_DISABLED_HOOKS so it can be
muted for diagnostics but is on by default.
- Stop-gate enforcement remains in PromptNexus (Haiku-flagged auditor);
this hook reports its availability so Claude knows whether the audit
loop is active. Slash command /karpathy-guidelines surfaces the same
content on demand.
3. Cross-container shared volume (claude-shared-state)
- .devcontainer/devcontainer.json mounts a docker named volume at
/mnt/claude-shared, which lives on the WSL2 .vhdx disk.
- scripts/lib/shared-volume.js populates it from a reachable PromptNexus
clone (PROMPT_NEXUS_PATH env var or standard fallbacks). Idempotent
and gracefully no-ops when no source is present.
- Lets sibling containers (prompt_hub, agent_harness, future projects)
read the same hooks, schemas, and verdict history.
Tests: 31 new tests across 4 files, all passing.
- tests/lib/mcp-prewarm.test.js (7)
- tests/lib/shared-volume.test.js (9)
- tests/scripts/setup.test.js (5)
- tests/hooks/btoo-directives.test.js (10)
Validators green: 27 hook matchers, 69 commands, agent.yaml + catalog
in sync (README/AGENTS counts updated to 69).
Docs:
- docs/STANDARDS-ENFORCEMENT.md - two-tier enforcement design
- docs/CONTAINER-PERSISTENCE.md - shared-volume architecture and ops
- CLAUDE.md, AGENTS.md - cloud session + mandatory standards sections
Reused (not duplicated): scripts/hooks/run-with-flags.js (canonical
wrapper), scripts/lib/utils.js, schemas/hooks.schema.json, the existing
node-e plugin-root bootstrap pattern from hooks/hooks.json.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 82c0d68e74
ℹ️ 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".
| if (!args.skipVerify) { | ||
| runVerify(); | ||
| } |
There was a problem hiding this comment.
Propagate verify failures as setup exit errors
main() always exits with 0 even when runVerify() reports a failed npm run verify, so provisioning can be marked successful while the harness is actually broken. This is a functional regression for any automation or devcontainer flow relying on scripts/setup.js exit status to detect readiness; currently the failure is only logged as a warning and then ignored.
Useful? React with 👍 / 👎.
| if (!args.skipVerify) { | ||
| runVerify(); | ||
| } | ||
|
|
||
| log(`Done. Try: 'npm run verify' or 'claude mcp list'. Created .env: ${envResult.created}.`); | ||
| process.exit(0); |
There was a problem hiding this comment.
🔴 setup.js ignores runVerify() return value, always exits 0 despite documented contract
The main() function in scripts/setup.js ignores the return value of runVerify() and unconditionally calls process.exit(0) at line 211. This contradicts the JSDoc at scripts/setup.js:14-15 which states "Exits 0 on success, 1 on hard failure (deps missing, verify fails)" and the caller .devcontainer/postCreate.sh:14-16 which says "It exits non-zero only on hard failures (deps missing, verify fails)." As a result, postCreate.sh:19's warning message ("WARN: scripts/setup.js exited non-zero") can never fire on verify failures, silently hiding boot-time verification problems.
| if (!args.skipVerify) { | |
| runVerify(); | |
| } | |
| log(`Done. Try: 'npm run verify' or 'claude mcp list'. Created .env: ${envResult.created}.`); | |
| process.exit(0); | |
| if (!args.skipVerify) { | |
| const verifyOk = runVerify(); | |
| if (!verifyOk) { | |
| log('WARN: verify failed. Exiting non-zero so the caller can surface the issue.'); | |
| process.exit(1); | |
| } | |
| } | |
| log(`Done. Try: 'npm run verify' or 'claude mcp list'. Created .env: ${envResult.created}.`); | |
| process.exit(0); |
Was this helpful? React with 👍 or 👎 to provide feedback.
| "UserPromptSubmit": [ | ||
| { | ||
| "hooks": [ | ||
| { | ||
| "type": "command", | ||
| "command": "node -e \"const p=require('path');const r=(()=>{var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var p=require('path'),f=require('fs'),h=require('os').homedir(),d=p.join(h,'.claude'),q=p.join('scripts','lib','utils.js');if(f.existsSync(p.join(d,q)))return d;for(var s of [[\\\"ecc\\\"],[\\\"ecc@ecc\\\"],[\\\"marketplace\\\",\\\"ecc\\\"],[\\\"everything-claude-code\\\"],[\\\"everything-claude-code@everything-claude-code\\\"],[\\\"marketplace\\\",\\\"everything-claude-code\\\"]]){var l=p.join(d,'plugins',...s);if(f.existsSync(p.join(l,q)))return l}try{for(var g of [\\\"ecc\\\",\\\"everything-claude-code\\\"]){var b=p.join(d,'plugins','cache',g);for(var o of f.readdirSync(b,{withFileTypes:true})){if(!o.isDirectory())continue;for(var v of f.readdirSync(p.join(b,o.name),{withFileTypes:true})){if(!v.isDirectory())continue;var c=p.join(b,o.name,v.name);if(f.existsSync(p.join(c,q)))return c}}}}catch(x){}return d})();const s=p.join(r,'scripts/hooks/plugin-hook-bootstrap.js');process.env.CLAUDE_PLUGIN_ROOT=r;process.argv.splice(1,0,s);require(s)\" node scripts/hooks/run-with-flags.js user-prompt-submit:btoo-directives scripts/hooks/btoo-directives.js standard,strict" | ||
| } | ||
| ], | ||
| "description": "Inject /karpathy-guidelines and Boil-the-Ocean completeness standard as additionalContext on every user prompt. Mute via ECC_HOOK_PROFILE=minimal or ECC_DISABLED_HOOKS=user-prompt-submit:btoo-directives.", | ||
| "id": "user-prompt-submit:btoo-directives" | ||
| } |
There was a problem hiding this comment.
🚩 UserPromptSubmit hook entry lacks matcher field unlike all other non-tool hooks
The new UserPromptSubmit entry in hooks/hooks.json:4-14 does not include a "matcher" field. Every other non-tool-specific hook in the file (SessionStart, PreCompact, Stop, SessionEnd) includes "matcher": "*". Without access to the Claude Code hook schema (referenced at the top of hooks.json), it's unclear whether omitting matcher is valid for UserPromptSubmit events or if it would cause the hook to silently not fire. The existing pattern strongly suggests "matcher": "*" should be added for consistency and safety.
Was this helpful? React with 👍 or 👎 to provide feedback.
| echo "[postCreate] Activating yarn 4.9.2 via corepack..." | ||
| corepack enable | ||
| corepack prepare yarn@4.9.2 --activate |
There was a problem hiding this comment.
📝 Info: postCreate.sh runs corepack twice — once directly, once via setup.js
Lines 9-11 of postCreate.sh call corepack enable and corepack prepare yarn@4.9.2 --activate directly. Then line 18 invokes node scripts/setup.js, which calls activateYarn() at scripts/setup.js:93-106 doing the exact same corepack commands again. This is idempotent and harmless but adds ~2-5 seconds of redundant work on every devcontainer build. The bash calls could be removed since setup.js now handles them, but the comment at line 3-5 suggests postCreate.sh intentionally keeps corepack setup in bash as a fallback in case setup.js fails before reaching that phase.
Was this helpful? React with 👍 or 👎 to provide feedback.
| const ASSETS_TO_COPY = [ | ||
| { type: 'dir', rel: 'hooks' }, | ||
| { type: 'file', rel: path.join('evals', 'verdict.schema.json') }, | ||
| { type: 'dir', rel: 'commands' }, | ||
| { type: 'dir', rel: 'evals/principles' } | ||
| ]; |
There was a problem hiding this comment.
📝 Info: ASSETS_TO_COPY uses inconsistent path construction
In scripts/lib/shared-volume.js:22-27, the ASSETS_TO_COPY array mixes path.join('evals', 'verdict.schema.json') on line 24 with a hardcoded forward-slash 'evals/principles' on line 26. While both work correctly because path.join normalizes forward slashes on all platforms, this inconsistency is a code smell. The convention in this file (and per AGENTS.md's cross-platform principle) would be to use path.join('evals', 'principles') for line 26 as well.
Was this helpful? React with 👍 or 👎 to provide feedback.
| function checkRequiredKeys() { | ||
| const env = readEnvKeys(ENV_PATH); | ||
| const missing = REQUIRED_KEYS.filter(k => !env[k] || env[k].length === 0); | ||
| if (missing.length === 0) { | ||
| log('Required keys present.'); | ||
| return { missing: [] }; | ||
| } | ||
| log(`WARN: required keys empty: ${missing.join(', ')}. MCP / API features will fail until set.`); | ||
| return { missing }; | ||
| } |
There was a problem hiding this comment.
📝 Info: checkRequiredKeys only reads .env file, not process.env
The checkRequiredKeys() function at scripts/setup.js:82-91 reads the .env file on disk and checks if ANTHROPIC_API_KEY and GITHUB_TOKEN have values. It does not check process.env, so keys injected via the container environment (e.g., Docker secrets, CI variables, or --env-file in devcontainer.json) will still trigger the warning. Since this is a non-fatal warning (not a hard failure), and the .env file is the primary configuration mechanism for the devcontainer flow, this is acceptable behavior. The warning just might be confusing for users who set keys via other mechanisms.
Was this helpful? React with 👍 or 👎 to provide feedback.
| "SESSION_SCRIPT": "./session-start.sh", | ||
| "CONFIG_FILE": "./mcp-config.json", | ||
| "ENABLE_VERBOSE_LOGGING": "false" | ||
| "ENABLE_VERBOSE_LOGGING": "false", |
There was a problem hiding this comment.
🚩 initializeCommand requires Node.js on the host machine
The initializeCommand in .devcontainer/devcontainer.json:32 runs node scripts/setup.js --init-env-only && (docker volume create claude-shared-state || true) on the host before the container builds. If the host doesn't have Node.js installed, this command fails, and the && prevents the docker volume from being created. The volume is referenced in the mounts array, so a missing volume could cause a container start failure. This is a known devcontainer limitation — the initializeCommand runs in the host environment. Users of cloud-based devcontainers (Codespaces, Cowork) typically have Node.js available, but local Docker Desktop users might not.
Was this helpful? React with 👍 or 👎 to provide feedback.
Summary
Three coordinated additions land in one shippable change:
scripts/setup.jsorchestrator: materializes.env, runs install, pre-warms MCP, populates the shared volume, runs verify. Wired as bothinitializeCommand(host-side) andpostCreateCommand(container-side) so a fresh clone boots in one command.scripts/hooks/btoo-directives.js) injects /karpathy-guidelines (4 rules) and the Boil-the-Ocean completeness standard (verbatim user phrasing) on every turn asadditionalContext. Profile-gated viaECC_HOOK_PROFILE/ECC_DISABLED_HOOKS. Stop-gate auditing remains owned by PromptNexus.claude-shared-statedocker named volume mounts at/mnt/claude-shared(lives on the WSL2 .vhdx).scripts/lib/shared-volume.jspopulates it from a reachable PromptNexus clone. Sibling containers see the same hooks, schemas, and verdict history.Files
Architecture decisions
Test plan
Out of scope
🤖 Generated with Claude Code