Cloud agent — autonomous issue → PR
Run Claude on a GitHub issue unattended and remotely, iterating until done, using Claude Code on the web (Anthropic-managed cloud sessions) instead of GitHub Actions.
Why this over GitHub Actions: the agent loop runs on Anthropic's VM and bills to the Max subscription's rate limits — 0 GitHub Actions minutes. Since 1 March 2026 self-hosted runners are metered on private repos, so the old @claude workflow (.github/workflows/claude.yml) is no longer free. That workflow is kept as a fallback but should be retired once this is trusted.
Flow
issue labelled `claude`
→ Routine fires a cloud session
→ session clones the repo (.claude/skills/start, CLAUDE.md, hooks all load)
→ setup script (cached): Playwright browsers + supabase CLI + warm pnpm store
→ SessionStart hook (scripts/cloud-session-start.sh):
pnpm install → start dockerd (vfs, iptables off) → supabase start
(all best-effort; writes /tmp/cloud-supabase-status = ready|failed)
→ prompt: /start <issue> → implements; verifies with live /verify-fe if
Supabase is `ready`, else the mock-mode suite;
opens a PR via the GitHub MCP tools (Closes #<n>)
→ Auto-fix watches ci.yml and pushes fixes until green — no human @claude
Sandbox reality (verified in a live session + community reports):
- No
ghCLI — GitHub goes through the built-inmcp__github__*tools + the git push proxy (scoped to the current branch).- Docker daemon isn't running by default but can be started with constrained-kernel flags (
--storage-driver=vfs --iptables=false --ip6tables=false; see anthropics/claude-code#29515). The SessionStart hook does this.- Bridge networking is unavailable, so a multi-container stack like
supabase startmay not fully come up. The hook attempts it and records the outcome in/tmp/cloud-supabase-status. When it fails, verification falls back toci.yml's mock-mode suite (unit + route + Playwright e2e with fakeVITE_SUPABASE_*), which needs no backend. If reliable local Supabase is a hard requirement, run the Routine against your own VPS environment instead (bridge networking works there).
One-time setup
1. Install the Claude GitHub App (required for Auto-fix)
Onboard at claude.ai/code using the GitHub App method (not just /web-setup). The App delivers the PR webhooks that Auto-fix needs. Install it on the ai-marketing repo. This also provides the built-in mcp__github__* tools and the git push proxy the session uses — no gh CLI or GH_TOKEN required.
2. Create a cloud environment + setup script
In the claude.ai web UI, add an environment (cloud icon → Add environment). The setup script runs once and its filesystem is cached (snapshotted), so keep it under ~5 minutes. Node, pnpm, Postgres, Docker binaries and ripgrep are pre-installed; the script adds Playwright browsers, the Supabase CLI, and warms the pnpm store:
#!/bin/bash
cd front-end && pnpm exec playwright install chromium # browser (cached into the snapshot)
npm install -g supabase # Supabase CLI (the hook runs `supabase start`)
cd "$(git rev-parse --show-toplevel)" && pnpm install # warm the pnpm store into the cache
Do NOT install
ghand do NOT start dockerd here.ghisn't in Ubuntu's repos (GitHub is handled by the MCP tools + git proxy). Starting dockerd in the setup script is reported to crash the environment — the SessionStart hook starts it per session instead.Network access: leave on the default Trusted level — it already allows npm,
mcr.microsoft.com(Playwright), Docker Hub (Supabase images), and GitHub. No custom allowlist needed.Environment variables (
.envformat, no quotes — visible to anyone who can edit the environment, so local/test values only, never production). Use the sameVITE_SUPABASE_URLandVITE_SUPABASE_API_KEYyour localfront-end/.env.localuses — these point at the local Supabase (this project runs it on port 5411, not the default 54321) so the live/verify-fepath works when Supabase boots, and they're harmless in mock mode (the value is ignored there). TheVITE_SUPABASE_API_KEYis a client publishable key, safe to place here.No
GH_TOKEN, no production secrets (there is no secrets store yet).
3. Repo-side wiring (already committed)
scripts/cloud-session-start.sh— SessionStart hook. No-op locally (CLAUDE_CODE_REMOTE != true); in the cloud it runspnpm install, starts the Docker daemon (constrained-kernel flags), and attemptssupabase start. All best-effort and non-fatal; the outcome is written to/tmp/cloud-supabase-status(ready|failed) for/startto branch on..claude/settings.json— registers the hook (see Apply the settings hook below if not yet present)..claude/skills/start/SKILL.md— whenCLAUDE_CODE_REMOTE=true,/startskips the~/.worktreesworktree and checks out in place, uses the GitHub MCP tools + git proxy instead ofgh, and verifies with live/verify-fewhen Supabase booted (/tmp/cloud-supabase-status=ready) or the mock-mode suite otherwise.
Apply the settings hook
Ensure .claude/settings.json contains:
{
"hooks": {
"SessionStart": [
{
"matcher": "startup|resume",
"hooks": [
{ "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/scripts/cloud-session-start.sh" }
]
}
]
}
}
4. Create the trigger Routine
Create a Routine pinned to the environment from step 2, prompt:
/start <issue number>
After opening the PR, enable auto-fix on it (or run /autofix-pr on the branch).
Trigger: a GitHub event filtered to issues labelled claude.
If issue-label events aren't a supported Routine trigger yet, use one of:
- a scheduled Routine that runs
/starton the newest openclaude-labelled issue (managed polling), or- manual kickoff from a terminal:
claude --remote "/start 612".Confirm the currently supported trigger types when you build this.
Daily use
- Open an issue (or label an existing one) with
claude. - The Routine spins up a cloud session; watch it at claude.ai/code or on mobile.
/startopens a PR; Auto-fix drivesci.ymlto green automatically.- Review and merge the PR like any other.
Caveats (research preview)
- Docker + local Supabase are best-effort. The daemon starts with constrained flags, but bridge networking is unavailable, so
supabase startmay fail. When it does, verification falls back to the mock-mode suite (unit + route + Playwright e2e with fakeVITE_SUPABASE_*) — the same checksci.ymlruns, needing no backend. For guaranteed local Supabase, use a VPS environment instead. First-sessionsupabase startis slow (pulls ~10 images); they cache to disk for subsequent sessions. - No
ghCLI — GitHub is reached via the built-inmcp__github__*tools and the git push proxy (scoped to the current branch). - Shared rate limits with all interactive Claude usage — parallel runs eat into your own quota.
- No secrets store — env vars are visible to environment editors; test/mock keys only.
- GitHub-only for pushing PRs.
- Auto-fix does not resolve merge conflicts (GitHub emits no webhook for them). If the base branch advances into a conflict, open the session and ask Claude to rebase.
- Resource ceiling: 4 vCPU / 16 GB RAM / 30 GB disk per session.
Verification checklist
- Start a session in the environment; run
check-tools— confirm Playwright chromium is present. (ghwill be absent — expected; GitHub is via MCP.) - Confirm the SessionStart hook ran:
node_modules/exists at the repo root and underfront-end/(provespnpm installexecuted). If it's missing, the.claude/settings.jsonhook isn't wired — see Apply the settings hook. Then checkcat /tmp/cloud-supabase-status:ready→ Docker + local Supabase came up;supabase statusshould work.failed→ inspect/tmp/dockerd.log; verification will use mock-mode. This is the expected outcome if the sandbox lacks bridge networking.
- Label a throwaway issue
claude; confirm the session checks out a branch without a~/.worktreesworktree, verifies (live/verify-feif Supabase isready, else the mock-mode suite), and opens a PR via the GitHub MCP tools. - Seed a failing check so
ci.ymlgoes red once; confirm Auto-fix pushes a fix and CI goes green with no human@claude. - Confirm
.github/workflows/claude.ymlandci.ymlare unchanged and the cloud run shows no compute charge (subscription usage only).
Fallbacks
.github/workflows/claude.yml— the metered self-hosted@claudepath.- A self-hosted Docker poller (own VPS) — only if preview limits prove blocking.