Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions .github/workflows/build-claude-code.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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 }}
Expand Down
12 changes: 10 additions & 2 deletions claude-code/.devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 3 additions & 3 deletions claude-code/.devcontainer/claude-sandbox/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
8 changes: 4 additions & 4 deletions claude-code/.devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
3 changes: 2 additions & 1 deletion claude-code/.devcontainer/init-plugins.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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) ────────────────────────────────────
Expand Down
14 changes: 14 additions & 0 deletions claude-code/.devcontainer/managed-settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "/usr/local/bin/patch-playwright-mcp"
}
]
}
]
}
}