Skip to content

Commit 0787ba2

Browse files
authored
refactor(claude-code): bake patch-playwright-mcp into the image (#87) (#88)
- COPY --chmod=0755 patch-playwright-mcp.sh into /usr/local/bin/ in the base stage so both default and sandbox targets inherit it - switch init-plugins.sh and both devcontainer.json templates to invoke /usr/local/bin/patch-playwright-mcp directly - drop the per-project script from README setup guidance and add the boundary rule: project-specific config stays per-project, universal logic moves into the image - add test -x check for the binary to all four CI verify matrix entries
1 parent 63650a3 commit 0787ba2

6 files changed

Lines changed: 30 additions & 20 deletions

File tree

‎.github/workflows/build-claude-code.yml‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,19 +76,19 @@ jobs:
7676
matrix:
7777
include:
7878
- image-suffix: claude-code
79-
verify-command: "bun --version || true && claude --version && mise --version && fish --version && rtk --version && ralphex --version"
79+
verify-command: "bun --version || true && claude --version && mise --version && fish --version && rtk --version && ralphex --version && test -x /usr/local/bin/patch-playwright-mcp"
8080
runner: ubuntu-24.04
8181
arch: amd64
8282
- image-suffix: claude-code
83-
verify-command: "bun --version || true && claude --version && mise --version && fish --version && rtk --version && ralphex --version"
83+
verify-command: "bun --version || true && claude --version && mise --version && fish --version && rtk --version && ralphex --version && test -x /usr/local/bin/patch-playwright-mcp"
8484
runner: ubuntu-24.04-arm
8585
arch: arm64
8686
- image-suffix: claude-code-sandbox
87-
verify-command: "claude --version && mise --version && fish --version && which iptables && rtk --version && ralphex --version"
87+
verify-command: "claude --version && mise --version && fish --version && which iptables && rtk --version && ralphex --version && test -x /usr/local/bin/patch-playwright-mcp"
8888
runner: ubuntu-24.04
8989
arch: amd64
9090
- image-suffix: claude-code-sandbox
91-
verify-command: "claude --version && mise --version && fish --version && which iptables && rtk --version && ralphex --version"
91+
verify-command: "claude --version && mise --version && fish --version && which iptables && rtk --version && ralphex --version && test -x /usr/local/bin/patch-playwright-mcp"
9292
runner: ubuntu-24.04-arm
9393
arch: arm64
9494
runs-on: ${{ matrix.runner }}

‎claude-code/.devcontainer/Dockerfile‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,13 @@ ENV MISE_TRUSTED_CONFIG_PATHS="/workspace"
137137
COPY --from=rtk-download /usr/local/bin/rtk /usr/local/bin/rtk
138138
COPY --from=ralphex-download /usr/local/bin/ralphex /usr/local/bin/ralphex
139139

140+
# ── Playwright MCP patch script ──────────────────────────────────────────────
141+
# Universal logic with no project-specific config — bake into the image so
142+
# consumer projects don't carry a copy. Invoked from postCreateCommand
143+
# (init-plugins.sh) and postStartCommand (devcontainer.json) to re-patch
144+
# plugin auto-updates between sessions. See issue #87.
145+
COPY --chmod=0755 patch-playwright-mcp.sh /usr/local/bin/patch-playwright-mcp
146+
140147
# ── Claude Code CLI ───────────────────────────────────────────────────────────
141148
# Using npm instead of the native installer (curl claude.ai/install.sh | bash).
142149
# The native installer is recommended for interactive use, but rate-limits (429)

‎claude-code/.devcontainer/claude-sandbox/devcontainer.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@
126126
// Firewall init (bind-mounted from project) + re-patch the Playwright MCP
127127
// plugin's .mcp.json. The patch runs on every start so plugin auto-updates
128128
// between sessions don't leave MCP pointing at the missing chrome channel.
129-
// See issue #85.
130-
"postStartCommand": "sudo /usr/local/bin/init-firewall.sh && bash .devcontainer/patch-playwright-mcp.sh",
129+
// See issues #85, #87.
130+
"postStartCommand": "sudo /usr/local/bin/init-firewall.sh && /usr/local/bin/patch-playwright-mcp",
131131
"waitFor": "postStartCommand"
132132
}

‎claude-code/.devcontainer/devcontainer.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@
114114
// Re-patch the Playwright MCP plugin's .mcp.json on every start.
115115
// Plugin auto-updates between sessions create new cache dirs whose .mcp.json
116116
// points at /opt/google/chrome/chrome (which we don't ship). Without this,
117-
// MCP silently breaks until the next image rebuild. See issue #85.
118-
"postStartCommand": "bash .devcontainer/patch-playwright-mcp.sh",
117+
// MCP silently breaks until the next image rebuild. See issues #85, #87.
118+
"postStartCommand": "/usr/local/bin/patch-playwright-mcp",
119119
"waitFor": "postCreateCommand"
120120
}

‎claude-code/.devcontainer/init-plugins.sh‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,9 @@ done
5858

5959
# ── Playwright MCP: route every cached .mcp.json to system chromium ─────────
6060
# Universal across arches (no Chrome stable binary in either default or sandbox).
61-
# Patch logic lives in patch-playwright-mcp.sh so it can also be invoked from
62-
# postStartCommand to catch plugin auto-updates between sessions (#85).
63-
bash "$(dirname "$0")/patch-playwright-mcp.sh"
61+
# The patch binary is baked into the image (see Dockerfile #87) and also runs
62+
# from postStartCommand to catch plugin auto-updates between sessions (#85).
63+
/usr/local/bin/patch-playwright-mcp
6464

6565
# ── rtk init (token-optimized CLI proxy) ────────────────────────────────────
6666
# Global hook-first mode: installs only the PreToolUse rewrite hook to ~/.claude/,

‎claude-code/README.md‎

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -78,20 +78,22 @@ Projects consuming these images need the following files in their repository.
7878

7979
Only pin tools that affect project stability — dev infrastructure (rtk, ralphex, Claude Code) is pre-installed in the image at latest. See [`mise.toml`](mise.toml) for a template.
8080

81-
### Optional: `.devcontainer/init-plugins.sh` and `.devcontainer/patch-playwright-mcp.sh`
81+
### Optional: `.devcontainer/init-plugins.sh`
8282

83-
Claude Code plugin initialization. `init-plugins.sh` registers marketplaces, installs plugins, and (via `patch-playwright-mcp.sh`) rewrites every cached Playwright MCP `.mcp.json` to launch the system chromium. Both scripts are idempotent. See [`.devcontainer/init-plugins.sh`](.devcontainer/init-plugins.sh) and [`.devcontainer/patch-playwright-mcp.sh`](.devcontainer/patch-playwright-mcp.sh) for templates.
83+
Claude Code plugin initialization. `init-plugins.sh` registers marketplaces, installs plugins, and invokes the image-baked `/usr/local/bin/patch-playwright-mcp` to rewrite every cached Playwright MCP `.mcp.json` to launch the system chromium. Idempotent. See [`.devcontainer/init-plugins.sh`](.devcontainer/init-plugins.sh) for the template.
8484

85-
Wire them into `devcontainer.json`:
85+
Wire into `devcontainer.json`:
8686

8787
```jsonc
8888
"postCreateCommand": "bash .devcontainer/init-plugins.sh",
89-
"postStartCommand": "bash .devcontainer/patch-playwright-mcp.sh"
89+
"postStartCommand": "/usr/local/bin/patch-playwright-mcp"
9090
```
9191

9292
`postStartCommand` re-runs the patch on every container start so plugin auto-updates between sessions cannot leave MCP pointing at the missing chrome channel. See the [Playwright Strategy](#playwright-strategy) section.
9393

94-
Mark as executable: `chmod +x init-plugins.sh patch-playwright-mcp.sh`
94+
Mark as executable: `chmod +x init-plugins.sh`
95+
96+
> **Why `init-plugins.sh` stays per-project but `patch-playwright-mcp` doesn't:** `init-plugins.sh` carries project-specific configuration (marketplace list, plugin list) — it's *meant* to be edited per project. The patch script has zero project-specific config and is identical across every consumer, so it's baked into the image and flows through the same daily-rebuild + `pull_policy: always` channel as the rest of the image. That boundary is the rule: project-specific config stays per-project; universal logic moves into the image.
9597
9698
### Sandbox-only: `.devcontainer/claude-sandbox/init-firewall.sh`
9799

@@ -183,7 +185,6 @@ The template includes extensions for Claude Code, Bun, OXC, Tailwind, YAML, Dock
183185
├── devcontainer.json ← default devcontainer
184186
├── docker-compose.yml ← default compose (image + pull_policy)
185187
├── init-plugins.sh ← Claude Code plugin setup (optional)
186-
├── patch-playwright-mcp.sh ← Playwright MCP .mcp.json patch (optional)
187188
└── claude-sandbox/
188189
├── devcontainer.json ← sandbox devcontainer
189190
├── docker-compose.yml ← sandbox compose (image + pull_policy)
@@ -252,8 +253,10 @@ export default defineConfig({
252253

253254
The `playwright@claude-plugins-official` plugin's `@playwright/mcp` defaults
254255
to the `chrome` channel (`/opt/google/chrome/chrome`), which the image does
255-
not ship. The included `patch-playwright-mcp.sh` rewrites every cached
256-
`.mcp.json` under `~/.claude/plugins/cache/` to use the system chromium:
256+
not ship. The image bakes in `/usr/local/bin/patch-playwright-mcp` (built
257+
from [`patch-playwright-mcp.sh`](.devcontainer/patch-playwright-mcp.sh)),
258+
which rewrites every cached `.mcp.json` under `~/.claude/plugins/cache/`
259+
to use the system chromium:
257260

258261
```json
259262
{
@@ -270,7 +273,7 @@ not ship. The included `patch-playwright-mcp.sh` rewrites every cached
270273
}
271274
```
272275

273-
`init-plugins.sh` invokes the patch script at `postCreateCommand`, and the
276+
`init-plugins.sh` invokes the patch binary at `postCreateCommand`, and the
274277
template `devcontainer.json` files run it again at `postStartCommand` so
275278
plugin auto-updates between sessions cannot leave MCP pointing at the
276279
missing chrome channel.

0 commit comments

Comments
 (0)