From d4bb2c63eecfaba1bc2d60fe7d1b4a1c180abe8f Mon Sep 17 00:00:00 2001 From: Declan Carroll Date: Mon, 10 Aug 2026 15:37:07 +0100 Subject: [PATCH] feat: Add Flaky MCP, Docker-in-Docker, and rotation-proof git auth to Codespaces (no-changelog) (#35950) Co-authored-by: Claude Fable 5 --- .devcontainer/codespaces/Dockerfile | 6 +++++ .devcontainer/codespaces/README.md | 20 ++++++++++++++- .devcontainer/codespaces/codespaces-env.sh | 9 +++++++ .../codespaces/codespaces-secrets.sh | 25 +++++++++++++------ .devcontainer/codespaces/devcontainer.json | 12 ++++++++- .devcontainer/codespaces/docker-compose.yml | 2 ++ .devcontainer/codespaces/gh-shim.sh | 7 ++++++ .../codespaces/gitcredential-refresh.sh | 6 +++++ 8 files changed, 78 insertions(+), 9 deletions(-) create mode 100644 .devcontainer/codespaces/codespaces-env.sh create mode 100644 .devcontainer/codespaces/gh-shim.sh create mode 100644 .devcontainer/codespaces/gitcredential-refresh.sh diff --git a/.devcontainer/codespaces/Dockerfile b/.devcontainer/codespaces/Dockerfile index d578997d2e0..0ac66a1bf08 100644 --- a/.devcontainer/codespaces/Dockerfile +++ b/.devcontainer/codespaces/Dockerfile @@ -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 diff --git a/.devcontainer/codespaces/README.md b/.devcontainer/codespaces/README.md index 9329aa86ae2..d06e68327fa 100644 --- a/.devcontainer/codespaces/README.md +++ b/.devcontainer/codespaces/README.md @@ -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 ` 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 diff --git a/.devcontainer/codespaces/codespaces-env.sh b/.devcontainer/codespaces/codespaces-env.sh new file mode 100644 index 00000000000..629bfa122d8 --- /dev/null +++ b/.devcontainer/codespaces/codespaces-env.sh @@ -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 /dev/null) && export "$key=$dec" - done /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 diff --git a/.devcontainer/codespaces/devcontainer.json b/.devcontainer/codespaces/devcontainer.json index 98db2fdb744..69423cc850c 100644 --- a/.devcontainer/codespaces/devcontainer.json +++ b/.devcontainer/codespaces/devcontainer.json @@ -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" } } } diff --git a/.devcontainer/codespaces/docker-compose.yml b/.devcontainer/codespaces/docker-compose.yml index a03fd5facb1..d7b8f96eb64 100644 --- a/.devcontainer/codespaces/docker-compose.yml +++ b/.devcontainer/codespaces/docker-compose.yml @@ -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 diff --git a/.devcontainer/codespaces/gh-shim.sh b/.devcontainer/codespaces/gh-shim.sh new file mode 100644 index 00000000000..3dd917bc2b2 --- /dev/null +++ b/.devcontainer/codespaces/gh-shim.sh @@ -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 "$@" diff --git a/.devcontainer/codespaces/gitcredential-refresh.sh b/.devcontainer/codespaces/gitcredential-refresh.sh new file mode 100644 index 00000000000..c49f8eb19de --- /dev/null +++ b/.devcontainer/codespaces/gitcredential-refresh.sh @@ -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 "$@"