Skip to content

Instantly share code, notes, and snippets.

@mphinance
Created June 3, 2026 21:52
Show Gist options
  • Select an option

  • Save mphinance/cc8107b9c7705c8a976cd31f61c8d983 to your computer and use it in GitHub Desktop.

Select an option

Save mphinance/cc8107b9c7705c8a976cd31f61c8d983 to your computer and use it in GitHub Desktop.
Autonomous Claude coding loop for GitHub: a nightly workflow that reads a SPEC.md, plans its own backlog, and ships one feature per run as a PR. Built on anthropics/claude-code-action. Setup + the copy-paste prompt included.

Autonomous Claude coding loop for GitHub

A nightly GitHub Actions loop that reads a SPEC.md, plans its own backlog, and ships one feature per run as a pull request. Built on the official anthropics/claude-code-action.

Two roles, auto-selected: initializer (turns your spec into an ordered feature_list.json) and coder (builds the next pending feature, runs tests first, opens a PR). One long-lived branch, one PR, so you don't wake up to a thousand branches.

I built this for my trading-data repo and wrote up the whole messy story here: https://mphinance.substack.com

What's in this gist

  • claude-autonomous.yml - the loop. Drop it in .github/workflows/.
  • SPEC.example.md - rename to SPEC.md, fill in what you want built.
  • setup-prompt.md - paste into Claude Code to have it wire all this up for you.

Setup, 5 steps

  1. Install the Claude GitHub App on your repo: https://github.com/apps/claude (or run /install-github-app inside Claude Code).
  2. Add the auth secret. Subscription: run claude setup-token, then gh secret set CLAUDE_CODE_OAUTH_TOKEN. (Prefer an API key? Set ANTHROPIC_API_KEY and change the workflow input to anthropic_api_key.)
  3. Your gh login needs the workflow scope to push workflow files: gh auth refresh -h github.com -s workflow.
  4. Add claude-autonomous.yml to .github/workflows/, rename SPEC.example.md to SPEC.md, and edit it.
  5. Run it: Actions tab, "Claude Autonomous Loop", Run workflow. It opens a PR. You merge.

The two gotchas that cost me an afternoon

1. The agent needs permission to act. In headless CI it cannot approve its own tool prompts, so without a permission mode it auto-denies everything and produces nothing. Ask me how I know: my first run flailed for 15 minutes, hit 68 permission denials, burned $2, and created zero files. The workflow sets --permission-mode bypassPermissions for exactly this reason.

2. So protect main instead. bypassPermissions is only safe because three things are true at once: the runner is an ephemeral sandbox that gets destroyed after each run, the agent only opens PRs (it never merges, you do), and branch protection blocks force-push/deletion to main. Turn on branch protection. The agent gets freedom inside the box, not the keys to the house.

Safety model in one line

The agent plans and writes in a throwaway sandbox; the only thing it can do to your repo is ask, by opening a PR. You are the gate.

MIT. If it helps you, that's the point.

name: Claude Autonomous Loop
# A self-driving coding loop on top of the official anthropics/claude-code-action.
# Give it a SPEC.md and it works through the spec one feature per run, stacking each
# onto ONE branch (claude/autonomous) and ONE open PR -- so you wake up to a single
# PR with N commits, not a pile of branches. Two roles, auto-selected:
#
# INITIALIZER (first run, no feature_list.json yet)
# Reads SPEC.md, breaks it into an ordered feature_list.json, writes
# claude-progress.txt, and opens the bootstrap PR.
#
# CODER (every run after that)
# Reads SPEC.md + feature_list.json + claude-progress.txt + recent git history,
# runs the existing tests for a baseline BEFORE writing code, implements the next
# pending feature, updates the notes, and opens/updates the one PR.
#
# It NEVER pushes to main. Every change lands as a PR you review and merge.
# Auth: CLAUDE_CODE_OAUTH_TOKEN secret (subscription) OR swap to anthropic_api_key.
# See README for the 5-step setup and the two gotchas that will save you an afternoon.
on:
workflow_dispatch:
schedule:
- cron: '0 9 * * *' # 09:00 UTC nightly; comment out to make it manual-only
concurrency:
group: claude-autonomous
cancel-in-progress: false
jobs:
loop:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
issues: write
id-token: write
actions: read
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Select role
id: role
run: |
if [ ! -f SPEC.md ]; then
echo "::error title=No SPEC.md::Create SPEC.md at the repo root describing what to build, then re-run."
exit 1
fi
if [ -f feature_list.json ]; then
echo "mode=coder" >> "$GITHUB_OUTPUT"
else
echo "mode=init" >> "$GITHUB_OUTPUT"
fi
- name: Initialize feature list
if: steps.role.outputs.mode == 'init'
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
prompt: |
You are the INITIALIZER for an autonomous coding loop. feature_list.json does not exist yet.
1. Read SPEC.md at the repo root. If it is empty/placeholder, leave a note and stop.
2. Read any CONTRIBUTING.md / AGENTS.md / CLAUDE.md for repo conventions.
3. Decompose SPEC.md into an ORDERED list of small, independently shippable features
(earlier features unblock later ones).
4. Write feature_list.json as an array of:
{"id": <int from 1>, "title": <short>, "description": <1-3 sentences>,
"status": "pending", "pr": null, "notes": ""}
5. Create claude-progress.txt with a header + an initialized entry (UTC date, 0/<N> done).
6. Use the SINGLE long-lived branch `claude/autonomous` (create from origin/main; if it or
an open "Autonomous loop" PR already exists, update it instead of duplicating). Commit
feature_list.json + claude-progress.txt, push, and ensure exactly ONE PR to main titled
"Autonomous loop". Do NOT implement any feature yet. Do NOT touch secrets or workflows.
claude_args: |
--permission-mode bypassPermissions
--max-turns 30
- name: Implement next feature
if: steps.role.outputs.mode == 'coder'
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
prompt: |
You are the CODER for an autonomous coding loop. Implement exactly ONE feature this run.
1. Read SPEC.md, feature_list.json, claude-progress.txt, and `git log -20 --oneline`.
2. Pick the FIRST feature with status "pending". If none, append an "all complete" note to
claude-progress.txt and stop with no other changes.
3. BASELINE FIRST: run the existing test suite (e.g. `pytest -q`, `npm test`) before you
touch anything. If there is no suite, say so and validate by running the affected code.
Never claim tests pass when none ran.
4. Implement ONLY that one feature, reusing existing patterns. Keep the diff tight.
5. Re-run tests / validation and confirm no regressions.
6. Update feature_list.json (status -> "done", fill "pr") and append a dated progress entry
describing what you did and how you validated it.
7. Land it on the SINGLE branch `claude/autonomous` with exactly ONE open PR titled
"Autonomous loop": if that PR is open, check out origin/claude/autonomous and stack your
commit; if not (first feature or prior PR merged), recreate from origin/main and open it.
If the feature is ambiguous or risky (schema change, data deletion, major dep bump), do NOT
implement it: set status "blocked" with a note, open an issue with a plan, and stop.
NEVER push to main, touch secrets, or edit .github/workflows/*.
claude_args: |
--permission-mode bypassPermissions
--max-turns 80

The one-prompt setup

Paste this into Claude Code (or the Claude GitHub App via @claude) in your own repo, and it will wire up the whole loop for you. Read its plan before you let it push.


Set up an autonomous coding loop in this repo using the official anthropics/claude-code-action@v1.

Create .github/workflows/claude-autonomous.yml that runs on workflow_dispatch and a nightly cron. In a first step, auto-select a role: if feature_list.json does not exist, run as INITIALIZER; otherwise run as CODER.

INITIALIZER: read SPEC.md at the repo root, decompose it into an ordered feature_list.json (array of {id, title, description, status:"pending", pr:null, notes}), write a claude-progress.txt, and open ONE pull request to main from a single long-lived branch named claude/autonomous. Do not implement any feature yet.

CODER: read SPEC.md, feature_list.json, claude-progress.txt, and recent git log; pick the first feature whose status is "pending"; run the existing test suite as a baseline BEFORE writing code; implement exactly that one feature; mark it done and update progress; then stack the commit onto the single claude/autonomous branch and keep exactly one open "Autonomous loop" PR (reuse it if open, recreate from main if the previous one was merged). One feature per run. Never push to main directly.

Authenticate with claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} and pass claude_args with --permission-mode bypassPermissions and a sane --max-turns (30 for the initializer, 80 for the coder). Give the job permissions: of contents, pull-requests, and issues write, plus id-token write and actions read.

Also create a starter SPEC.md with a short goal and an ordered feature list whose first item is "add a test scaffold," and add an .gitignore exception so feature_list.json is not ignored if a blanket *.json rule exists.

Then stop and tell me the exact manual steps to finish: installing the Claude GitHub App, setting the CLAUDE_CODE_OAUTH_TOKEN secret, refreshing gh auth with the workflow scope, and turning on branch protection for main (block force-push and deletion).

SPEC - Autonomous Loop Work Queue

Rename this to SPEC.md at your repo root. This is the brief the loop reads. On the first run it gets decomposed into feature_list.json; after that the loop ships one feature per run as a PR. Edit this file to steer what gets built.

Rules the loop respects:

  • Every change ships as a PR to main. Never a direct push. You merge.
  • One feature per run. Tight, scoped diffs.
  • Reuse existing patterns; match the surrounding style.
  • Never commit secrets. No edits to .github/workflows/*.
  • Validate by running tests or the affected code. Don't claim tests passed when none ran.

Goal

(One or two sentences: what you want built or hardened, and what must NOT change.)

Features

  1. Test scaffold. Add a test runner and one real test against an existing pure function. This unblocks the "run tests first" step for every later feature.
  2. (Your next feature.) Keep them small and ordered so each unblocks the next.
  3. ...

Out of scope (do not touch without a new spec)

  • (List the dangerous areas: payments, auth, migrations, deploy scripts, vendored code.)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment