My day-to-day dev loop is built around ccmagic, my Claude Code plugin of dev workflow skills. The core of it is an interactive ticket lifecycle: Claude does the work, and I stay in the loop at each stage to answer questions and approve decisions. /ccmagic:work-ticket ENG-123 picks up a Linear (or JIRA) ticket, classifies it, branches, implements, self-reviews, and opens a PR. /ccmagic:review-ticket reviews the change against the ticket's actual scope and acceptance criteria. /ccmagic:pr-feedback triages review comments on the PR (from humans or agents), and /ccmagic:finish-ticket sanity-checks and merges.
Recently I added /ccmagic:auto-ticket, which takes me out of that loop entirely. Give it a ticket ID and it runs the whole lifecycle unattended, including the merge. This post covers what it does, how I've tried to keep it from merging garbage, and when I would and wouldn't use it.
What it does
auto-ticket is an orchestrator. It chains the existing lifecycle skills in autonomous mode:
auto-ticket ENG-123
→ work-ticket implement, self-review, open the PR
→ review-ticket ticket-grounded review; fix CRITICAL findings, re-review (looped)
→ pr-feedback apply fixes, reply to threads, file follow-ups, push,
validate locally, wait for CI + bot reviews (looped)
→ finish-ticket merge gate: mergeable + CI green + no unaddressed change requests
→ summary posted to the PR and the ticketEvery run ends one of two ways:
- Merged. The PR merged, the ticket moved to Done, and a run summary was posted to both the PR and the ticket.
- Parked. Nothing merged. The ticket moved to a configured
needs-humanstate (or got aneeds-humanlabel), with a comment explaining exactly what decision it's waiting on.
There's no third outcome, on purpose. It doesn't sit waiting for input that isn't coming, and it doesn't merge on a guess. If the work is uncertain it parks with a note, and I count that as the system working: it figured out a human was needed.
This is built for solo projects where merging with no human in the loop is the whole point. If that makes you nervous, good. The "when you shouldn't" section below is for you.
How it's built
Some of this was in the original design, but most of the hardening came from a habit I now use on everything: run the workflow for real, then hand Claude the full session transcripts and have it look for bugs, gaps, and wasted work in its own runs. That's how I found the pattern-fixing and delta-review problems below, plus smaller things like duplicate summary posts on retried steps. If you're building autonomous workflows, read the transcripts. Better yet, make the agent read them.
One contract, six skills
The orchestrator and every sub-skill it calls share a small written contract. Each invocation gets a "grounding block" prepended to its arguments: the ticket ID, tracker, PR, run ID, and loop bounds. Each sub-skill ends its output with a machine-readable status handshake:
status: clean | fixable-findings | needs-human | done
reason: <one line, when not clean/done>
follow_ups: [anything filed or deferred]clean means the step ran and found nothing left to fix (a review pass with no CRITICAL findings, feedback fully addressed). done means the step completed a terminal action, like the merge itself. fixable-findings sends the orchestrator into the relevant fix loop, and needs-human routes to a park.
The orchestrator parses the last handshake and decides what to do next. A sub-skill that crashes or emits no handshake is treated as needs-human. That one rule removes a whole class of silent failures, because an ambiguous result can't turn into a merge.
If the ticket can't be found, stop
One rule sits above everything else, and it's written into the skill in exactly these terms: if the ticket can't be found, stop. Don't infer, guess, or make up what the ticket might contain. The ticket system is the source of truth.
That rule exists because I watched it happen. In early runs, when a ticket lookup failed, Claude happily invented a plausible scope from the ticket ID and the repo and started implementing it. An autonomous system that writes its own requirements and then merges against them is worse than no automation at all, so a failed lookup is now a hard stop.
Per-step subagents on per-step models
Each step runs in its own forked subagent on the model that fits it: Opus for implementation and review, where judgment matters; Sonnet for PR feedback, validation, and the finish step; Haiku for the mechanical commit-and-push. This keeps the orchestrator's context small across a long run and spends the expensive model time where it pays off. Every step's model can be overridden per repo.
That routing is also the cost story. I run on a Claude Max subscription, so I don't see a per-run dollar figure, but the shape of the spend is deliberate: one Opus implementation pass, up to a few Opus review passes, and cheap models for everything mechanical. On API pricing the same structure applies. If cost matters to you, the per-repo model overrides are the knob and the loop caps below are the ceiling.
Every loop has a cap
Every loop has a cap and every cap has a defined exit. The review-fix loop runs at most 3 passes. The PR-feedback loop runs at most 3 passes. Local validation gets 2 fix attempts. The CI wait has a configurable timeout (30 minutes by default). Hitting any cap means park the ticket, post the reason, and exit.
Reviews that fix the class, and reviews that diff
Two more lessons from real runs made it into the review loop. First, when a review finding is one instance of a repeatable pattern (say, versioned asset references that a cache-bust missed), the fix loop has to fix the whole class: apply the fix to every instance, re-run the search that found them, and only then re-review. One early run merged fine, but only just, because it point-fixed a few instances instead of looking for the rest.
Second, re-reviews produce delta reports. A fresh review subagent has no memory of the previous pass, so the orchestrator hands it the list of findings it just fixed. The re-review checks each one off as fixed or not in a line each and only reports new findings in full. Without this, pass 2 re-argues pass 1 and the loop burns its budget rediscovering things.
The audit trail
Every run posts a summary to the PR and the ticket: how it classified the ticket, what decisions it made without asking, review passes and findings, feedback applied and declined, follow-up tickets filed, and the final outcome. If you can't audit unattended automation afterward, you'll stop trusting it the first time something looks off. The summary carries a run ID and posting is idempotent, so a retried step never double-posts.
When you should use it
auto-ticket pays off in direct proportion to the quality of your tickets and your safety nets. It's the right tool when most of these are true:
- You'd merge it yourself without much thought. Well-scoped bug fixes, small features, dependency-adjacent chores, test additions, refactors with existing coverage. The kind of ticket where your own review would be a skim.
- The ticket has real acceptance criteria. The review step grades the work against the ticket's stated scope. A ticket that says "fix the thing" gives it nothing to grade against, so ambiguity turns into a park at best, or a plausible-but-wrong implementation that your CI then has to catch.
- CI is trustworthy. The merge gate is "mergeable, CI green, no unaddressed change requests." If your CI is green when the code is broken, you've handed the merge decision to a rubber stamp. Your tests are the reviewer now, so they need to be good.
- You're solo, or the repo's norms allow bot merges. The whole design assumes no human review is required before merge.
- Volume is the problem. The backlog of small, clear, low-risk tickets that never get picked up because each one costs thirty minutes of context switching. This is where an autonomous driver earns its keep, especially running headless (more on that in the next post).
When you shouldn't
- No CI, or decorative CI. This is the hard disqualifier. Every other safety property funnels through "CI is green" at the merge gate. Without meaningful automated tests,
auto-ticketis an expensive way to merge unreviewed code. - High blast radius. Auth, billing, data migrations, anything security-sensitive, anything irreversible. The system parks when it's uncertain, but the failure you care about here is confident and wrong, and no handshake protocol catches that. These tickets get a human even when they look simple.
- Untrusted people can write to your ticket tracker or your PRs. Once you go autonomous, the ticket body and the review comments are instructions that an agent with merge rights will act on. That makes them part of your trust boundary, and prompt injection is the attack. On a solo private repo with a private tracker, the only person who can inject is you. On a public repo where anyone can file issues or comment on PRs, an unattended agent that implements ticket content and applies reviewer feedback is asking for trouble. If outsiders can write to the inputs, keep a human on the merge.
- Product decisions in disguise. "Improve the onboarding flow" is a design conversation, not a ticket. Autonomous mode will either park it or make product decisions you didn't delegate.
- Team repos where review is the point. If PR review is how your team shares context and catches design drift, automating the author *and* skipping the human reviewer removes both sides of that conversation. (Using
work-ticketinteractively and keeping a human reviewer is a fine middle ground.) - A codebase Claude doesn't know yet. ccmagic's review and implementation skills lean on documented conventions (
context/conventions.md, architecture knowledge from/ccmagic:map-codebase). On a brownfield repo with none of that written down, run the interactive loop for a while first. You're teaching the harness, and yourself, what good looks like there. - You can't write down what "done" means. If you can't state acceptance criteria, it's still an idea, not a ticket. Ideas belong in the interactive loop.
Turning it on
/ccmagic:auto-ticket {ID} always runs autonomously. The knobs live in .claude/ccmagic.local.md per repo (with a ~/.claude/ccmagic.local.md user file for personal defaults):
needs_human_state: Blocked # where parked tickets go
needs_human_label: needs-human # fallback label when the state doesn't exist
max_feedback_passes: 3
# max_review_fix_passes: 3
# max_validate_attempts: 2
# ci_timeout_minutes: 30Everything autonomous is additive and opt-in. The interactive paths of every skill are unchanged, and nothing runs autonomously without an explicit signal.
What's next
Running this from my laptop is useful. Running it with no laptop involved is the actual goal: a Linear ticket gets a FullAuto label, a self-hosted worker picks it up in a container, and I come back later to either a merged PR and a Done ticket, or a parked ticket with a note explaining why. That's Cyrus plus ccmagic's transport layer, and it's the next post.
ccmagic is open source: github.com/devondragon/ccmagic. Install it from the Claude Code plugin marketplace with
/plugin marketplace add devondragon/ccmagic
then
/plugin install ccmagic@ccmagic

No comments yet