rig-task — implement one unit of work end-to-end
Take a single unit of work — a tracker issue or an ad-hoc "implement this"
with no issue filed — code it test-first, open a PR, drive it to a clean review,
and hand back. Never auto-merges unless --auto-merge is passed (then CI
gates the merge — see Step 5). This is the orchestrator over the kit's
building blocks: it delegates isolation to /rig-worktree, review to
/rig-review, and the fix loop to /rig-review fix, so each concern lives in
one place.
It's the single-unit sibling of /rig-epic (one unit vs many interleaved), and
mirrors its lifecycle: start gets you to an open PR, finish drives
the review to clean. By default a bare invocation runs both in one continuous
session — the phases exist for when you want to open the PR, let CI / the review
bot run, and finish later. /rig-sprint calls this once per item.
Configuration
Reads .rig/config.json (defaults in parentheses):
tracker.provider—linear|github|none(none). Selects how the spec is loaded and the PR is linked.none→ the item is an ad-hoc task description; no ticket calls.tracker.ticketPrefix— e.g.ABC-; used to infer a ticket from the branch.tracker.githubIntegration— whentrue, GitHub drives PR/merge transitions (In Review, Done); rig still sets the start-of-work "In Progress" (Step 1), which GitHub can't observe before a PR exists. Whenfalse/absent, you may move states freely.tracker.labelMapFile(.claude/label-mapping.md) — label source of truth.tracker.board— GitHub Projects v2 identity (owner, projectNumber, statusOptions, closingKeyword). Used at PR-open (Step 4) to link the PR and move the board via therig-trackeradapter. Absent → no board steps.vcs.baseRef(origin/main),vcs.defaultBranch(main),vcs.branchConvention({user}/{ticket}-{slug}),vcs.protectedBranchMergeQueue(false).runtime.packageManager/runtime.installCommand— passed to/rig-worktree.test.command(npm test),test.integrationCommand,test.e2eCommand.review.patternsFile(.claude/REVIEWER.md),review.bot(none),review.botRetrigger,review.maxRounds(5).style.guideFile(.claude/STYLE.md) — the house style for the commit message, the PR body, and the hand-back line.agents.architect/agents.qa/agents.coder/agents.reviewer(defaultrig-<role>) — the registered agent names for each role.
Unconfigured fallback: with no profile, this runs with tracker none (ad-hoc
task), npm test, base origin/main, and a local-only review loop.
Subcommands (phases)
$ARGUMENTS begins with an optional phase, then a target:
- (no phase) — run
startthenfinishin one continuous session. The common case: "implement this." start [<issue-id> | "<description>"]— Steps 1–5: load/decide the spec, set up a worktree, TDD to green, pre-PR self-review, and open the PR. Then stop. Use to kick a unit off and let CI / the review bot run while you move on.finish [<issue-id | PR#>]— Steps 6–7: resolve the open PR (from the arg, the current branch, or the single open PR for this unit), drive the review-bot loop to a terminal state, and hand back. Resumes a unitstart(or a teammate) opened.
Target resolution:
- An
<issue-id>matchingtracker.ticketPrefix(and tracker ≠ none) → load the spec from the tracker. - A quoted
"<description>"(or tracker = none) → ad-hoc: the description is the spec; no tracker record required. Optionally offer to file an issue at the end. - Omitted → infer from the current branch's
{tracker.ticketPrefix}<N>token; else ask.
--local — in the finish phase, force the local /rig-review fix loop even if
a cloud auto-fix workflow is enabled (see Step 6).
--base <ref> — branch the worktree from, and target the PR at, <ref> instead
of vcs.baseRef. A caller like /rig-epic passes the integration branch here so
the child stacks on it rather than the trunk.
--auto-merge — once the pre-PR self-review (Step 4.5) is clean, squash-merge
the PR: gh pr merge <N> --squash --delete-branch --auto so it lands when CI /
required checks pass — CI is the merge gate, not a human. Always squash —
into a stacked integration branch or the trunk (don't rebase-merge; many repos
disallow it). If there are no required checks (--auto can't arm / the PR is
already mergeable), merge directly (drop --auto). If vcs.protectedBranchMergeQueue
is true, pass --auto with no method flag (the queue decides). Without this
flag the skill never merges — it opens the PR and hands back. /rig-epic passes
--auto-merge per child so each lands on the integration branch on its own.
--spec-cleared — skip Step 2's blocking pause: run the spec review for its notes
but do not stop for a human to resolve blockers. Use when the caller already
front-loaded and cleared the spec (e.g. /rig-epic run's front-loaded spec review
resolved every child's blockers up front), so this child doesn't re-pause on a
question already answered. Spec review still informs RED/GREEN; it just never gates.
Print the resolved unit + phase as the first output line, e.g.
rig-task start: ABC-369 (from branch alice/abc-369-...) or
rig-task start: ad-hoc "add dark mode".
The steps below are grouped into the two phases. Steps 1–5 are start;
Steps 6–7 are finish. A bare rig-task runs all of them in order.
Step 1 — Load the spec and set up a worktree
Base guard — before any worktree or agent work. If this item is an epic child — its tracker parent is an epic, or an integration branch
<parent-slug>-*for its parent already exists on origin (git ls-remote --heads origin) — and no stacked--basewas given, stop now: report that it must stack on<integration-branch>(or be driven by/rig-epic), and do not set up a worktree or spend spec-review/TDD. This catches an epic child mistakenly launched against the trunk before any agent runs. (When--baseis supplied, a child is expected — proceed.)Load the spec.
tracker: linear→ fetch the issue via the Linear MCPget_issue; the description + acceptance criteria are the spec. Set the issue to "In Progress" now (Linear:save_issuewithstate: "In Progress") — work is starting. Do this even whentracker.githubIntegrationis true: a GitHub integration can't observe local work until a PR exists, so this is the one transition it can't drive. It's idempotent (only move if not already started). rig then ensures In Review / Done idempotently later (Steps 5/7) — deferring to a live integration if it already moved them, driving them itself if nothing did (see the adaptive note in Step 5).tracker: github→ fetch the issue viagh issue view.tracker: none→ the argument/task description is the spec; if it's a one-liner, ask the user to expand the acceptance criteria. Restate the acceptance criteria to yourself before coding.
Set up an isolated checkout via
/rig-worktree(don't inlinegit worktree add— the skill owns fetch-before-branch, env symlinks, and per-worktree install). Branch name:tracker: linearand the payload carries a suggested per-user branch name (gitBranchName) → use it verbatim (it's what a tracker↔GitHub integration matches on for auto-state).- Otherwise build from
vcs.branchConvention:{user}= git user,{ticket}= the ID (or a kebab slug of the task in ad-hoc mode),{slug}= short kebab of the title. Base fromvcs.baseRef.cdinto the printed worktree path ($WT); use it as{worktree-path}in the agent prompts below.
Name the session so a background-job list reads as the work, not the prompt (Claude-Code-only; no-ops elsewhere). Pass
--namethrough to/rig-worktree—"FEAT: <title> (<ticket>)"for new behavior/bug fixes (conventional-commitfeat:/fix:),"CHORE: <title> (<ticket>)"for maintenance (chore:/refactor:/test:/docs:). Add--skip-if-prefix "EPIC:"so an epic label set by/rig-epicwins when this runs as a child.
Step 2 — Spec review
Launch the architect and qa agents in parallel (names via agents.*):
- architect: "Review this spec for implementability. Identify ambiguities, missing acceptance criteria, files that need to change, and a suggested implementation order. Spec:\n{title}\n\n{description}"
- qa: "Review this spec from a testing perspective. What test cases are needed? Are the acceptance criteria testable? What edge cases matter? Spec:\n{title}\n\n{description}"
If either flags something that should be fixed before coding, clarify it — and,
in tracker mode, update the item's description with the clarification and tell
the user what changed. With --spec-cleared (a caller like /rig-epic run
already front-loaded and resolved the specs), skip this pause: keep the architect
- qa notes to inform RED/GREEN, but do not stop for blockers — proceed straight to Step 3.
Step 3 — RED: tests first
Launch qa to write tests against the spec, before any implementation:
- qa: "Write tests for this item. Cover every acceptance criterion + the edge cases the architect flagged. Don't stub or comment out — the tests must compile and fail for the right reason (missing implementation), not a syntax error. Worktree: {worktree-path}. Match the project's test framework and colocation conventions.\n\nSpec:\n{title}\n\n{description}\n\nArchitect notes:\n{architect_output}"
Run the suite from the worktree: cd "$WT" && <test.command>.
Verify red. The new tests must fail with a message that reflects the missing behavior (e.g. "expected X, got undefined" / "not-yet-implemented symbol"). A new test that passes immediately was pinning existing behavior — send it back to qa. A syntax error is a qa bug; fix it before moving on.
Step 4 — GREEN: minimum implementation
Launch coder with the failing tests as the spec:
- coder: "Implement {item} to make the failing tests pass. Worktree: {worktree-path}. Explore the affected files first and prefer extending or reusing existing code over a parallel implementation. Make the minimum change — no features not required by a test.\n\nSpec:\n{title}\n{description}\n\n Architect notes:\n{architect_output}\n\nFailing tests:\n{test_output}"
Re-run the suite. If still red, send coder a fix iteration with the current failures. Max 3 iterations; if still red after 3, stop and surface to the user — the spec is likely wrong or there's an unstated constraint. If implementing exposes a missing edge case, add that test first (loop back to RED for it) rather than piling untested behavior into GREEN.
Step 4.25 — REFACTOR (optional, only while green)
If the GREEN code has obvious duplication, awkward naming, or a helper that wants extracting, spawn coder once more — "no new behavior, tests stay green" — and re-run the suite. Skip if the code is already clean.
Step 4.5 — Pre-PR self-review (gate before push)
The point: pre-empt the PR review bot — every finding caught here is one you don't pay a round-trip on. Delegate so the gate lives in one place:
- Find. Run
/rig-review {vcs.baseRef}for this item (in{worktree-path}). It walksreview.patternsFileagainstgit diff {vcs.baseRef}...HEADand returns a triaged P0–P3 list. - Classify and fix:
- No P0/P1 → proceed to Step 5 (P2/P3 are fine to ship; report them).
- Any P0/P1 → run
/rig-review fix --source local --base {vcs.baseRef}in{worktree-path}. It spawns coder per finding, re-runs/rig-review, and loops to clean (maxreview.maxRounds). If still P0/P1 after the last round it returnsunresolved— stop, surface to the user, don't push.
- Report the final state before pushing, e.g.
tests: green, review: 0 P0/P1 + 2 P2 (deferred).
Step 5 — Push and open the PR
- Commit all changes with a message referencing the item.
- Push the branch.
- Open a PR (
gh pr create) targetingvcs.defaultBranch(or the base from Step 1 if a stacked base was used):- Title carries the item ID where a tracker is used (e.g.
feat(ABC-18): …), so it's visible in the PR list, not just the body. - Body includes: a summary; the tracker link (
Fixes ABC-18for Linear /Closes #<n>for GitHub — the keyword that auto-links and, withtracker.githubIntegration, auto-transitions state); a test plan; and an## Architecturesection stating any new abstraction/package/ dependency/migration or core-domain touch and why existing code wasn't reused (write "No architectural change." otherwise). - Write both to
style.guideFile(default.claude/STYLE.md): commit subject in the imperative under ~72 characters, PR summary that answers what changed and why in its first sentence, a test plan another person could run. Don't narrate the diff — the diff is right there. - Proof the draft body before creating the PR —
/rig-proof findon it (pipe the draft in on stdin) and apply what comes back. Every reviewer reads this body; rewriting it after it's posted notifies all of them again.
- Title carries the item ID where a tracker is used (e.g.
- Link the PR + move to In Review — adaptively (works with or without a
live tracker↔GitHub integration; treat
tracker.githubIntegrationas a hint, not a gate — it may claimtruewhile nothing is actually connected):tracker: linear:- Link:
get_issue; if no attachment already references this PR URL (a live integration may have added one),create_attachmentwith the PR URL. - In Review:
get_issue; if the item is not already In Review or beyond, move it there (save_issue). If the integration already advanced it, leave it — never clobber a further-along state.
- Link:
tracker: github: go through the tracker adapter (resolver:<TRACKER>=.rig/rig-trackerif executable else<RIG_DIR>/scripts/rig-tracker.sh; seedocs/tracker-adapter.md):- Link:
<TRACKER> link-pr <issue#> <pr#>— idempotently ensures the PR closes/links the issue (Step 3 may already have written theCloses #<n>;link-pris a no-op then). This is what moves the board to Done on merge via the Project's built-in automation. - In Review: if
tracker.boardis configured,<TRACKER> set-status <issue#> "<tracker.board.statusOptions.inReview>"— no GitHub event moves a card to In Review, so the adapter does it. Skip if no board is configured. Ensure the item's labels matchtracker.labelMapFileif that file exists.
- Link:
- Capture the PR number for Step 6.
Merge behavior. Without --auto-merge, do NOT gh pr merge — open the PR
and hand back (the human, /rig-sprint, or /rig-epic decides). With
--auto-merge, the self-review is clean, so enable gh pr merge <N> --squash --auto
now (always squash — see the --auto-merge flag doc above); CI/required checks are
the merge gate — the PR lands on its own when they pass. Either way, don't hand-add PR
labels if the project ships a PR-labeler workflow — it applies them.
── finish phase (Steps 6–7) ──
When resuming via rig-task finish (rather than a one-shot run), first
re-establish context: resolve the open PR and its worktree from the target
arg / current branch / the single open PR for this unit, and cd into that
worktree before continuing.
Step 6 — Review-bot loop: watch (default) or drive (--local)
review.bot: none→ nothing to do; the Step 4.5 local gate was the whole review. Go to Step 7.- A cloud auto-fix workflow is enabled (the project installed
ci/workflows/auto-review-fix.ymland its enable marker) and--localwas not passed → watch: do not fix or push. Poll the PR (~60s intervals, up to ~30 min) until the bot's review reaches a terminal state and report it. Running a local driver alongside a cloud one collides (both push, both re-trigger), so only watch here. - Otherwise (drive) → delegate to
/rig-review fix <PR>. It owns the bot protocol forreview.bot(poll + classify the bot's review, fix via coder, commit, push, re-trigger viareview.botRetrigger), up toreview.maxRounds. Take its returned outcome as this step's result:clean— reviewed with no actionable issues (merge gates take over).actionable— still has feedback after the last round; left for a human.timeout— bot didn't respond in time; left for a human.
Only clean is merge-green; everything else stops for a human.
Step 7 — Hand back
Print the final outcome line and the PR URL. The outcome line carries real
state, not reassurance — tests: 412 pass, 0 fail · review: 0 P0/P1, 2 P2 deferred · PR #418 open, never "everything looks good".
Tracker state — adaptive (don't
trust tracker.githubIntegration; check reality): if the PR has already merged
by hand-back (e.g. --auto-merge landed it), ensure the item is Done
(get_issue; move only if not already Done — defer if a live integration closed
it). If the PR is still open, leave it In Review — the merge event (a later
finish run, /rig-epic's merge gate, or a live integration) moves it to Done.
Never clobber a further-along state.
If invoked by /rig-sprint, return the outcome string so the caller can decide
whether to merge, and skip any local-QA offer (the caller makes it once).
Error handling
- Item can't be loaded/fetched → tell the user immediately.
- Branch already exists → ask whether to continue on it or start fresh.
- Test command times out or crashes (not just failures) → report and stop before opening a PR.
gh pr createfails because a PR already exists → capture that PR's number and skip to Step 6.
Env files in worktrees
A worktree is a fresh checkout — gitignored env files aren't carried over, and a
missing secret often reads as a flaky/timeout test failure. Step 1's /rig-worktree
setup symlinks gitignored env files from the main repo into the worktree before
any command runs. To re-link by hand, re-run the worktree setup with --reuse.