Two issues found running /ralph-loop end-to-end on a real project: 1. single-pr mode mirrored the toolkit's autopromote rule (blockers must be `Done`), which halts after each phase since tickets stay `In progress` until the final PR merges. That contradicts the README's "one PR for the whole DAG" semantics. Now `single-pr` counts a blocker resolved at status `In progress | In review | Done` so the loop completes the whole DAG in one invocation. `per-ticket` keeps the strict `Done` rule. 2. v1 failure handling treated all failures as ticket-failures and parked the issue as `Needs human` with a failure comment — wrong for environmental failures (Claude usage limit, network, interrupt) where the issue itself is fine. Now distinguished: env failures reset issue to `Ready` + un-assign; ticket failures park as before. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
10 KiB
| name | description |
|---|---|
| ralph-loop | Run a GitHub Project (v2) of issues against the current repo using Claude Code subagents — one fresh subagent per ticket. Subscription-only alternative to `agentic-toolkit run` (which uses sandcastle + Docker + Anthropic API). Use after `/to-project` once the project is ready. |
Ralph Loop
Implement a GitHub Project's issues by dispatching each Ready issue to a fresh Claude Code subagent (subagent_type=general-purpose). Each subagent gets a clean context window — the "ralph loop" property that keeps long-running multi-ticket work coherent.
This skill is the subscription-based counterpart to agentic-toolkit run. Pick the path that fits your setup:
| Path | Auth | Sandbox | Cost | Parallelism |
|---|---|---|---|---|
agentic-toolkit run (sandcastle) |
ANTHROPIC_API_KEY |
Docker container | API metered | Per-container |
/ralph-loop (this skill) |
Claude Code subscription | None — host repo | Subscription flat | Serial (one per Claude session) |
Trade-offs you accept by using this skill:
- No sandbox isolation. The subagent has full tool access on your host repo. Run on a clean checkout with no uncommitted personal work.
- Serial only — one ticket at a time per Claude Code session. Cannot parallelise across machines.
- Visible: every tick happens in the foreground Claude Code UI. You can interrupt anytime.
Inputs
Confirm the following before starting. If anything is missing, ask the user.
- Project number (e.g.
2). - Mode —
per-ticketorsingle-pr. Same semantics as the runner CLI. - Owner — defaults to current
ghrepo owner (gh repo view --json owner --jq .owner.login). - Repo — defaults to current
ghrepo (gh repo view --json name --jq .name). - Target repo path — the local checkout the subagent will work in. Defaults to the current working directory.
- Base branch — defaults to current HEAD of the target repo (
git -C <path> rev-parse --abbrev-ref HEAD).
Pre-flight:
gh auth statusshows scopesproject,read:project,repo. Ifprojectmissing, rungh auth refresh -s project,read:project --hostname github.com.- The target repo working tree is clean (
git -C <path> status --porcelainis empty). If not, halt and tell the user.
Algorithm
The loop runs in the current Claude Code conversation. The orchestrator (you) coordinates state via gh and dispatches one subagent per ticket via the Agent tool.
1. Read project state
Run a GraphQL query equivalent to the runner's PROJECT_QUERY:
query($owner: String!, $repo: String!, $number: Int!) {
repository(owner: $owner, name: $repo) {
projectV2(number: $number) {
id
field(name: "Status") { ... on ProjectV2SingleSelectField { id options { id name } } }
items(first: 100) {
nodes {
id
fieldValues(first: 20) {
nodes { ... on ProjectV2ItemFieldSingleSelectValue { name field { ... on ProjectV2SingleSelectField { name } } } }
}
content {
... on Issue { id number title body labels(first: 20) { nodes { name } } assignees(first: 5) { nodes { login } } }
}
}
}
}
}
}
Parse each item to: { number, nodeId, itemId, title, body, labels, status, blockedBy, kind } where:
kind = "HITL"if labels includehitlorready-for-human, else"AFK".blockedBy= numbers extracted from the## Blocked bysection of the body.
If the Status field is missing any of the six required options (Backlog, Ready, In progress, In review, Needs human, Done), halt and tell the user to run /to-project.
2. Autopromote
Mode-specific blocker-resolution rule:
per-ticket— a blocker counts as resolved only if its Status isDone(work has merged). Mirrors the toolkit CLIautopromoteAfkexactly.single-pr— a blocker counts as resolved if its Status is one of{ In progress, In review, Done }. The shared branch already carries the blocker's commits, so downstream tickets can stack on top within the same invocation.
For each issue with kind === "AFK" AND status === "Backlog" AND every blockedBy is either resolved (per the rule above) OR not present in this project: set its Status to Ready.
This rule lets single-pr mode complete the whole DAG in one invocation, matching the README's "one PR for the whole DAG" semantics. per-ticket mode keeps the strict gate so reviewers see one merged PR before downstream work begins.
3. Compute current phase + pick next
Topologically partition issues by Blocked by (issues with all blockers in earlier phases form phase N). The current phase = lowest-indexed phase with any non-Done issue.
Within the current phase, pickNextReady = the lowest-numbered issue with status === "Ready" AND kind === "AFK".
If no such issue:
single-pr: jump to step 9 (finalise).per-ticket: exit with "Phase N complete. Merge open PRs, then re-invoke."
4. Claim
- Set the issue's Status to
In progress. - Assign to the current viewer:
gh api user --jq .login->gh issue edit <N> --add-assignee <login>(best-effort; ignore failure).
5. Prepare branch
Project slug = <repo>-p<projectNumber> (matches CLI's projectSlugFrom).
Branch name:
per-ticket:claude/<projectSlug>/<issueNumber>-<title-slug>(slug = lowercased, non-alphanumeric ->-, trimmed, max 50 chars).single-pr:claude/<projectSlug>(reused across tickets).
In the target repo:
git fetch origin
git checkout <base-branch>
git pull
if branch exists: git checkout <branch>
else: git checkout -b <branch>
6. Dispatch subagent
Use the Agent tool. subagent_type=general-purpose. The prompt MUST be fully self-contained — the subagent has no memory of this conversation.
Prompt template:
You are implementing GitHub issue #<NUMBER> in <OWNER>/<REPO>.
# Issue title
<TITLE>
# Issue body
<BODY>
# Working environment
- Target repo path: <TARGET-REPO-PATH>
- You are on branch: <BRANCH-NAME>
- Branch base: <BASE-BRANCH>
- Do NOT change branches. Do NOT push. Do NOT open PRs. The orchestrator handles those.
# Your task
1. Implement the issue's acceptance criteria fully.
2. If the repo has a test suite, run it and resolve failures before reporting complete.
3. Make commits as you go using the repo's commit style. Don't squash; the orchestrator may amend.
4. If you encounter ambiguity that blocks progress (e.g. unclear acceptance criteria, missing context, conflicting requirements), commit any progress made and report blocked rather than guessing.
# Reporting back
Your FINAL message must be exactly one of:
- `TICKET_COMPLETE: <one-line summary of what was implemented>`
- `TICKET_BLOCKED: <one-line reason and what's needed to unblock>`
Do not include any text after that line. The orchestrator parses it.
7. Validate result
After the subagent returns:
- Read the last line of the subagent's final message.
- Count commits:
git -C <target> rev-list --count <base-branch>..HEAD. - Success = subagent reported
TICKET_COMPLETEAND commit count > 0. - Otherwise: failure (treat
TICKET_BLOCKED, missing tag, or zero commits all as failure).
8. After-success actions
per-ticket:
git -C <target> push -u origin <branch>.gh pr create --base <base> --head <branch> --title "<title> (#<N>)" --body "Closes #<N>\n\nImplemented by /ralph-loop.".- Set Status =
In review. - Loop back to step 1 (state may have changed; re-read).
single-pr:
- Leave branch local — no push, no PR yet.
- Loop back to step 1.
9. Finalise (single-pr only)
When pickNextReady returns nothing:
commits = git -C <target> rev-list --count <base>..<branch>.- If
commits === 0: halt with "No commits to PR. Nothing was implemented." - Else:
git push -u origin <branch>; open ONE PRImplement project #<projectNumber>with body listingCloses #<number>for every non-Done issue. Set those issues' Status toIn review. - Print PR URL.
Failure handling (v1)
Halt on first failure. No retry. Distinguish two kinds of failure:
Environmental failure — subagent didn't get a fair chance (Claude usage limit hit, network drop, runtime crash, user interrupt). The issue itself isn't blocked.
- Reset the issue's Status from
In progressback toReady. - Un-assign the viewer (
gh issue edit <N> --remove-assignee @me). - Print:
Halted on issue #<N> (environmental: <reason>). Re-invoke when conditions are clear. - Do NOT post a
Needs humancomment — nothing for a human to action.
Ticket failure — subagent reported TICKET_BLOCKED, returned without TICKET_COMPLETE, or produced zero commits. The issue needs human investigation.
- Set the failing issue's Status to
Needs human. - Post a comment on the issue:
### Automated run failed (/ralph-loop) <subagent's TICKET_BLOCKED reason or "Subagent returned without completing"> <details><summary>Last subagent message</summary> <last 6000 chars of subagent's final message> </details> - Stop the loop. Print:
Halted on issue #<N>: <reason>. Tell the user to fix the underlying issue and re-invoke.
Future versions can add retry logic mirroring failure-handler.ts in the toolkit's src/modules/.
Idempotency
Re-invoking the skill is safe and resumes from current state:
- Issues already
Doneare excluded from phase computation. - Issues
In revieware skipped (their PR is awaiting human merge). - The loop picks up at the next
Readyissue.
In single-pr mode, the shared branch is reused across invocations — do not delete it between runs unless you're abandoning the project.
Notes
- The skill needs
ghCLI authed withproject + read:project + reposcopes. The runner's CLI shares the same requirement; if you've already usedagentic-toolkit runon the same machine, you're set. - This skill must run with
BashandAgenttools enabled. The subagent uses standardgeneral-purposepermissions (file edit, bash, etc.). - For privacy/security, the subagent prompt includes the full issue body — do not put secrets in issue descriptions.
- The orchestrator (you, the calling Claude session) keeps full conversation context across the entire loop. Only the subagents reset. If the orchestrator's context fills up, the user can
/clearand re-invoke; idempotency handles resumption.