diff --git a/.github/workflows/build-claude-code.yml b/.github/workflows/build-claude-code.yml index 56fd07b..df0f806 100644 --- a/.github/workflows/build-claude-code.yml +++ b/.github/workflows/build-claude-code.yml @@ -6,6 +6,7 @@ on: paths: - "claude-code/.devcontainer/Dockerfile" - "claude-code/.devcontainer/*.sh" + - "claude-code/.devcontainer/managed-settings.json" schedule: - cron: '13 11 * * *' workflow_dispatch: @@ -76,19 +77,19 @@ jobs: matrix: include: - image-suffix: claude-code - verify-command: "bun --version || true && claude --version && mise --version && fish --version && rtk --version && ralphex --version && test -x /usr/local/bin/patch-playwright-mcp && printenv AGENT_BROWSER_EXECUTABLE_PATH | grep -qx /usr/bin/chromium" + verify-command: "bun --version || true && claude --version && mise --version && fish --version && rtk --version && ralphex --version && test -x /usr/local/bin/patch-playwright-mcp && jq -e '.hooks.SessionStart[0].hooks[0].command == \"/usr/local/bin/patch-playwright-mcp\"' /etc/claude-code/managed-settings.json >/dev/null && printenv AGENT_BROWSER_EXECUTABLE_PATH | grep -qx /usr/bin/chromium" runner: ubuntu-24.04 arch: amd64 - image-suffix: claude-code - verify-command: "bun --version || true && claude --version && mise --version && fish --version && rtk --version && ralphex --version && test -x /usr/local/bin/patch-playwright-mcp && printenv AGENT_BROWSER_EXECUTABLE_PATH | grep -qx /usr/bin/chromium" + verify-command: "bun --version || true && claude --version && mise --version && fish --version && rtk --version && ralphex --version && test -x /usr/local/bin/patch-playwright-mcp && jq -e '.hooks.SessionStart[0].hooks[0].command == \"/usr/local/bin/patch-playwright-mcp\"' /etc/claude-code/managed-settings.json >/dev/null && printenv AGENT_BROWSER_EXECUTABLE_PATH | grep -qx /usr/bin/chromium" runner: ubuntu-24.04-arm arch: arm64 - image-suffix: claude-code-sandbox - verify-command: "claude --version && mise --version && fish --version && which iptables && rtk --version && ralphex --version && test -x /usr/local/bin/patch-playwright-mcp" + verify-command: "claude --version && mise --version && fish --version && which iptables && rtk --version && ralphex --version && test -x /usr/local/bin/patch-playwright-mcp && jq -e '.hooks.SessionStart[0].hooks[0].command == \"/usr/local/bin/patch-playwright-mcp\"' /etc/claude-code/managed-settings.json >/dev/null" runner: ubuntu-24.04 arch: amd64 - image-suffix: claude-code-sandbox - verify-command: "claude --version && mise --version && fish --version && which iptables && rtk --version && ralphex --version && test -x /usr/local/bin/patch-playwright-mcp" + verify-command: "claude --version && mise --version && fish --version && which iptables && rtk --version && ralphex --version && test -x /usr/local/bin/patch-playwright-mcp && jq -e '.hooks.SessionStart[0].hooks[0].command == \"/usr/local/bin/patch-playwright-mcp\"' /etc/claude-code/managed-settings.json >/dev/null" runner: ubuntu-24.04-arm arch: arm64 runs-on: ${{ matrix.runner }} diff --git a/claude-code/.devcontainer/Dockerfile b/claude-code/.devcontainer/Dockerfile index 54ea1ba..6c588a7 100644 --- a/claude-code/.devcontainer/Dockerfile +++ b/claude-code/.devcontainer/Dockerfile @@ -140,10 +140,18 @@ COPY --from=ralphex-download /usr/local/bin/ralphex /usr/local/bin/ralphex # ── Playwright MCP patch script ────────────────────────────────────────────── # Universal logic with no project-specific config — bake into the image so # consumer projects don't carry a copy. Invoked from postCreateCommand -# (init-plugins.sh) and postStartCommand (devcontainer.json) to re-patch -# plugin auto-updates between sessions. See issue #87. +# (init-plugins.sh), postStartCommand (devcontainer.json), and the Claude Code +# SessionStart hook (managed-settings.json below) — the hook closes the gap +# where the plugin auto-updates mid-container-run. See issues #87, #98. COPY --chmod=0755 patch-playwright-mcp.sh /usr/local/bin/patch-playwright-mcp +# ── Claude Code managed settings ───────────────────────────────────────────── +# Image-policy settings at the Linux managed location (highest precedence, +# outside any volume mount). Wires patch-playwright-mcp as a SessionStart hook +# so cache dirs created by mid-session plugin auto-updates get patched before +# the next session reads them. See issue #98. +COPY --chmod=0644 managed-settings.json /etc/claude-code/managed-settings.json + # ── Claude Code CLI ─────────────────────────────────────────────────────────── # Using npm instead of the native installer (curl claude.ai/install.sh | bash). # The native installer is recommended for interactive use, but rate-limits (429) diff --git a/claude-code/.devcontainer/claude-sandbox/devcontainer.json b/claude-code/.devcontainer/claude-sandbox/devcontainer.json index 11a93ad..2144c65 100644 --- a/claude-code/.devcontainer/claude-sandbox/devcontainer.json +++ b/claude-code/.devcontainer/claude-sandbox/devcontainer.json @@ -124,9 +124,9 @@ // Chromium is baked into the sandbox image (firewall blocks runtime install). "postCreateCommand": "sudo find /workspace -maxdepth 4 -name node_modules -type d -exec chown node {} + && sudo chown -R node /home/node/.claude && mise install && bun install", // Firewall init (bind-mounted from project) + re-patch the Playwright MCP - // plugin's .mcp.json. The patch runs on every start so plugin auto-updates - // between sessions don't leave MCP pointing at the missing chrome channel. - // See issues #85, #87. + // plugin's .mcp.json. The patch is defense in depth alongside the SessionStart + // hook in /etc/claude-code/managed-settings.json, which handles the case where + // the plugin auto-updates mid-container-run. See issues #85, #87, #98. "postStartCommand": "sudo /usr/local/bin/init-firewall.sh && /usr/local/bin/patch-playwright-mcp", "waitFor": "postStartCommand" } diff --git a/claude-code/.devcontainer/devcontainer.json b/claude-code/.devcontainer/devcontainer.json index cb67bd1..a4b4062 100644 --- a/claude-code/.devcontainer/devcontainer.json +++ b/claude-code/.devcontainer/devcontainer.json @@ -111,10 +111,10 @@ // Chromium is baked into the image via apt — no playwright install step needed. // Projects' playwright.config.ts should use process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH. "updateContentCommand": "sudo find /workspace -maxdepth 4 -name node_modules -type d -exec chown node {} + && sudo chown -R node /home/node/.claude && mise install && bun install", - // Re-patch the Playwright MCP plugin's .mcp.json on every start. - // Plugin auto-updates between sessions create new cache dirs whose .mcp.json - // points at /opt/google/chrome/chrome (which we don't ship). Without this, - // MCP silently breaks until the next image rebuild. See issues #85, #87. + // Re-patch the Playwright MCP plugin's .mcp.json on every start. Defense in + // depth alongside the SessionStart hook in /etc/claude-code/managed-settings.json, + // which handles the case where the plugin auto-updates mid-container-run. + // See issues #85, #87, #98. "postStartCommand": "/usr/local/bin/patch-playwright-mcp", "waitFor": "postCreateCommand" } diff --git a/claude-code/.devcontainer/init-plugins.sh b/claude-code/.devcontainer/init-plugins.sh index 2b42f73..85bf2c6 100644 --- a/claude-code/.devcontainer/init-plugins.sh +++ b/claude-code/.devcontainer/init-plugins.sh @@ -59,7 +59,8 @@ done # ── Playwright MCP: route every cached .mcp.json to system chromium ───────── # Universal across arches (no Chrome stable binary in either default or sandbox). # The patch binary is baked into the image (see Dockerfile #87) and also runs -# from postStartCommand to catch plugin auto-updates between sessions (#85). +# from postStartCommand (#85) and a Claude Code SessionStart hook (#98) — the +# hook is what closes the gap when the plugin auto-updates mid-container-run. /usr/local/bin/patch-playwright-mcp # ── rtk init (token-optimized CLI proxy) ──────────────────────────────────── diff --git a/claude-code/.devcontainer/managed-settings.json b/claude-code/.devcontainer/managed-settings.json new file mode 100644 index 0000000..e95097e --- /dev/null +++ b/claude-code/.devcontainer/managed-settings.json @@ -0,0 +1,14 @@ +{ + "hooks": { + "SessionStart": [ + { + "hooks": [ + { + "type": "command", + "command": "/usr/local/bin/patch-playwright-mcp" + } + ] + } + ] + } +}