feat: Add Flaky MCP, Docker-in-Docker, and rotation-proof git auth to Codespaces (no-changelog) (#35950)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Declan Carroll 2026-08-10 15:37:07 +01:00 committed by GitHub
parent ec92ffcd56
commit d4bb2c63ee
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 78 additions and 9 deletions

View File

@ -16,8 +16,14 @@ RUN npx -y playwright@${PLAYWRIGHT_VERSION} install-deps chromium
ENV DISABLE_AUTOUPDATER=1
RUN npm install -g @anthropic-ai/claude-code opencode-ai
COPY codespaces-env.sh /usr/local/lib/codespaces-env.sh
COPY codespaces-secrets.sh /etc/profile.d/codespaces-secrets.sh
# git and gh must survive token rotation: these read the token on each call
COPY gitcredential-refresh.sh /usr/local/bin/gitcredential-refresh.sh
COPY gh-shim.sh /usr/local/bin/gh
RUN chmod +x /usr/local/bin/gitcredential-refresh.sh /usr/local/bin/gh
RUN echo node ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/node && chmod 0440 /etc/sudoers.d/node
RUN mkdir -p /workspaces && chown node:node /workspaces
RUN corepack enable

View File

@ -6,7 +6,8 @@ from anywhere with a terminal, resume tomorrow.
This is a separate devcontainer config from the laptop one in
`.devcontainer/` — it ships both agent CLIs, `tmux` for session persistence,
and Playwright system deps, on the same Postgres sidecar setup.
Playwright system deps, and Docker-in-Docker (for testcontainers and
`pnpm --filter n8n-containers services`), on the same Postgres sidecar setup.
## One-time setup (~5 min)
@ -38,6 +39,15 @@ pnpm session rm # delete the codespace
- First codespace creation takes ~20 min uncached (image + full build). After
that, sessions attach instantly; new worktrees cost a `pnpm install` (~1–2 min).
## Flaky tools (MCP)
Claude sessions get the `flaky` MCP server automatically: Currents
flaky/quarantine data, the `qa_*` BigQuery dataset, Sentry RCA, live Linear,
and repo investigation. Login registers it from the repo-level
`FLAKY_MCP_TOKEN` / `FLAKY_MCP_URL` secrets. Forks have no secrets and skip
it. Tell the agent to call `get_flaky_context` first — it returns the rules
the tools assume.
## Viewing the dev UI locally
Two terminal windows:
@ -84,6 +94,14 @@ After a stop, `pnpm session <name>` restarts the codespace (~30–60 s); run
agent injects them into VS Code sessions only; they're delivered
base64-encoded to `/workspaces/.codespaces/shared/.env-secrets`. The image's
profile shim exports them for ssh/tmux sessions.
- **`git push` / `gh` return 401 in tmux and long sessions** — same root
cause as the secrets gotcha, plus rotation: Codespaces refreshes the
on-disk `GITHUB_TOKEN` every few minutes, so a login-time snapshot goes
stale. The image fixes both: a credential helper reads the current token
on each `git push` (`gitcredential-refresh.sh`), and a shim at
`/usr/local/bin/gh` does the same for `gh`. The token is scoped to
`n8n-io/n8n`: fork-based flows do not work, push branches directly. Do
not add SSH keys as a workaround — they have no per-repo granularity.
- Codespaces created by org members on this repo are **org-owned and
org-billed** (organization ownership + a monthly Codespaces budget are
enabled for n8n-io). Codespaces created before that change, or by

View File

@ -0,0 +1,9 @@
# Codespaces delivers secrets base64-encoded in a shared env file. It injects
# them into VS Code sessions only. Other shells must source this file to get
# them. Tokens rotate every few minutes: read at time of use, not at login.
if [ -f /workspaces/.codespaces/shared/.env-secrets ]; then
while IFS='=' read -r key val; do
case "$key" in '' | *[!A-Za-z0-9_]*) continue ;; esac
dec=$(printf %s "$val" | base64 -d 2>/dev/null) && export "$key=$dec"
done </workspaces/.codespaces/shared/.env-secrets
fi

View File

@ -1,8 +1,19 @@
# Codespaces delivers user secrets base64-encoded to a shared env file and only
# injects them into VS Code sessions; ssh/tmux login shells must source them here.
if [ -f /workspaces/.codespaces/shared/.env-secrets ]; then
while IFS='=' read -r key val; do
case "$key" in '' | *[!A-Za-z0-9_]*) continue ;; esac
dec=$(printf %s "$val" | base64 -d 2>/dev/null) && export "$key=$dec"
done </workspaces/.codespaces/shared/.env-secrets
# Login-shell setup for ssh/tmux sessions: export secrets, then do the
# one-time registrations.
. /usr/local/lib/codespaces-env.sh
# Register the Flaky MCP server for Claude Code. The config keeps a literal
# ${FLAKY_MCP_TOKEN}; Claude Code expands it at connect time, so the token is
# not written to disk. Forks have no repo secrets and skip this.
if [ -n "$FLAKY_MCP_URL" ] && ! grep -qs '"flaky"' "$HOME/.claude.json" && command -v claude >/dev/null 2>&1; then
claude mcp add --scope user --transport http flaky "$FLAKY_MCP_URL" \
--header 'Authorization: Bearer ${FLAKY_MCP_TOKEN}' >/dev/null 2>&1 || true
fi
# Register the credential helper in the user config, because Codespaces
# regenerates the managed /etc/gitconfig. When the env token is missing, the
# system helper exits 0 without output, and git falls through to ours.
if command -v git >/dev/null 2>&1 &&
! git config --global --get-all credential.helper 2>/dev/null | grep -qs gitcredential-refresh; then
git config --global --add credential.helper /usr/local/bin/gitcredential-refresh.sh
fi

View File

@ -12,7 +12,10 @@
"features": {
"ghcr.io/devcontainers/features/github-cli:1": {},
// gh codespace ssh needs an SSH server inside the container
"ghcr.io/devcontainers/features/sshd:1": {}
"ghcr.io/devcontainers/features/sshd:1": {},
// Docker for testcontainers and `pnpm --filter n8n-containers services`;
// needs `privileged: true` on the n8n service (compose-based config)
"ghcr.io/devcontainers/features/docker-in-docker:2": {}
},
"forwardPorts": [8080, 5678],
// Runs in prebuilds, so a fresh codespace starts with deps installed and the build warm
@ -23,6 +26,13 @@
},
"CLAUDE_CODE_OAUTH_TOKEN": {
"description": "Alternative to an API key: token from `claude setup-token` (Max subscription)"
},
// Both set at the repository level — team members get them automatically
"FLAKY_MCP_TOKEN": {
"description": "Bearer token for the Flaky (QA assistant) MCP endpoint"
},
"FLAKY_MCP_URL": {
"description": "URL of the Flaky MCP endpoint on the internal n8n instance"
}
}
}

View File

@ -15,6 +15,8 @@ services:
build:
context: .
dockerfile: Dockerfile
# Required by the docker-in-docker devcontainer feature
privileged: true
volumes:
- ../..:/workspaces:cached
command: sleep infinity

View File

@ -0,0 +1,7 @@
#!/bin/sh
# gh reads its token from the env only. Long sessions hold a stale snapshot of
# the rotated token. Read the current token on each call, then run the real gh
# (the github-cli feature installs it at /usr/bin/gh).
. /usr/local/lib/codespaces-env.sh
[ -n "$GITHUB_TOKEN" ] && export GH_TOKEN="$GITHUB_TOKEN"
exec /usr/bin/gh "$@"

View File

@ -0,0 +1,6 @@
#!/bin/sh
# The stock helper reads $GITHUB_TOKEN from the env. Non-login shells do not
# have it, and the token rotates. Read the current token, then delegate.
[ "$1" = get ] || exit 0
. /usr/local/lib/codespaces-env.sh
exec /.codespaces/bin/gitcredential_github.sh "$@"