From bugfix
Use as the post-execution stage of the autonomous bug-fix loop. Verifies local tests pass, pushes the branch, opens a PR via `bugfix:ticket-adapter`, comments the ticket with the PR link, and advances state to ci-watching. Dispatched by `bugfix:run-ticket` when `state.current_stage == "finishing"`.
How this skill is triggered — by the user, by Claude, or both
Slash command
/bugfix:autonomous-finishingThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
The post-execution stage: turn committed work into a public PR with a ticket comment, then hand off to CI watching.
The post-execution stage: turn committed work into a public PR with a ticket comment, then hand off to CI watching.
This skill is invoked by bugfix:run-ticket when state.current_stage == "finishing". Before doing any work:
.bugfix/runs/<ticket-id>.json. Confirm current_stage == "finishing". If not, exit with an error.state.worktree_path. All operations from here run inside the worktree.BEFORE pushing or opening a PR, verify the project's tests pass locally. executing-plan should have ensured this, but autonomous-finishing runs an independent confirmation — the cost is cheap, the cost of opening a broken PR is high.
The skill detects the test command using these heuristics (in order):
package.json exists with a test script: npm testCargo.toml exists: cargo testpyproject.toml exists with pytest: uv run pytest (run uv sync first if .venv is missing)go.mod exists: go test ./...Makefile has a test target: make testbugfix:block-and-comment(tech-failure, reason="could not detect a test command").If the detected command exits non-zero, refuses to proceed: exit via bugfix:block-and-comment(tech-failure, reason="tests fail at finishing — executing-plan should have caught this", artifacts=[test output]). This is an invariant violation (executing-plan should never advance with failing tests), but defensive against it nonetheless.
bugfix:ticket-adapter:push(branch) — push the branch from state.branch (e.g., fix/<ticket-id>).bugfix:ticket-adapter:open_pr(branch, base, title, body) — open the PR. base is state.base_branch. title and body per the template below.bugfix:ticket-adapter:ticket_comment(issue_number, body) — comment on the source ticket with the PR link.Construct the PR body using exactly this template:
Closes #<issue_number>
## What changed
<one-paragraph summary of the diff, in your own words, NOT inside untrusted-input tags>
## Why
<from the spec at state.spec_path — the "Problem statement" section, paraphrased; quoting ticket text wrap in <untrusted-input>>
## Regression test
The failing-test-first task from the plan (Task 1) added a regression test at: `<test path>`. This test fails on `<base_branch>` (commit `<base_sha>`) and passes on this PR's tip.
## Plan and review history
- Spec: `<state.spec_path>`
- Plan: `<state.plan_path>`
- Final code-reviewer pass: clean (per executing-plan's final review step)
🤖 Opened by bugfix autonomous loop. CI watching and a calibrated final review run next; this comment will be supplemented with the reviewer's verdict before merge-ready.
The ## Regression test section in the template above is rendered ONLY when state.artifacts.regression_test_path is non-null. This field is set by executing-plan from the **Regression test file:** declaration in Task 1; bug-class plans always have it, but improvement-class plans may legitimately omit a regression test (see bugfix:executing-plan's "Classification-aware Task 1 marker handling").
state.artifacts.regression_test_path is non-null (typical for bug PRs): include the paragraph as shown, substituting <test path> with state.artifacts.regression_test_path and <base_sha> with state.base_sha.state.artifacts.regression_test_path is null (typical for improvement PRs without a regression test): OMIT the entire ## Regression test heading and its paragraph from the rendered PR body. Do NOT render with empty placeholders — literal <test path> text leaking into a PR body is a bug. The other sections (## What changed, ## Why, ## Plan and review history) are unaffected.The PR title prefix is derived from state.artifacts.intake_classification:
bug → Fix: <issue title>improvement → Improve: <issue title><issue title> is the original GitHub issue title (already wrapped in <untrusted-input> by ticket-adapter:read). Strip the wrapper tags ONLY for the title field (titles must be plain text in gh pr create / mcp__github__create_pull_request); keep all body fields wrapped per the Untrusted-input rule.
PR title (full form): <prefix> #<issue_number>: <ticket title> where <prefix> is Fix or Improve per the rule above and <ticket title> is the ticket title sanitized for human display: strip the <untrusted-input> wrapper tags (the title is human-visible in the GitHub UI, not LLM-consumed at this point), strip any control characters, replace newlines with spaces, and truncate to 70 chars including an ellipsis. The wrapper-stripping is unusual for adapter-returned text — explicitly: the title is the one place we render ticket text for human reading, so the LLM-safety wrapper would just appear as literal <untrusted-input> characters in the PR header.
The ticket comment uses a shorter template (substitute <pr_url> with the constructed state.pr_url value):
PR opened: <pr_url>
The bugfix autonomous loop has executed the plan and opened a PR. CI watching and the PR-level final review (single calibrated reviewer) run automatically next; you'll see another comment when the loop reaches a terminal verdict.
state.pr_number = <returned by open_pr> (integer)state.pr_url = "https://github.com/<state.owner>/<state.repo>/pull/<state.pr_number>" (constructed string — ticket-adapter:open_pr returns only the integer, so this skill is responsible for assembling the URL form for downstream consumers and ticket-comment templates)state.updated_at = <now>state.current_stage = "ci-watching"One read-modify-write at the end.
Emit via bugfix/lib/events-append.sh ".bugfix/runs/<ticket-id>.events.log" <event> finishing '<detail-json>':
pr_pushed (detail: {}) — after a successful push, before open_pr.pr_opened (detail: {"pr_number": <int>}) — after open_pr returns.| Condition | exit_kind |
|---|---|
| Test command not detected | tech-failure |
| Local tests fail | tech-failure (invariant violation) |
ticket-adapter:push returns error | tech-failure |
ticket-adapter:open_pr returns error | tech-failure |
ticket-adapter:ticket_comment returns error | tech-failure (PR is open but ticket not updated — operator must reconcile) |
On success: write state.current_stage = "ci-watching", exit. bugfix:run-ticket dispatches bugfix:ci-watchdog, which long-polls CI on the new PR via ticket-adapter:ci_watch and either advances to pr-reviewing on green, fixes failures (bounded retries), or blocks on timeout.
Your work as the autonomous-finishing stage is done. You MUST stop here. Your next action MUST be to resume the next iteration of bugfix:run-ticket's driver loop (read the state file, check terminal/blocked, let the loop dispatch the next stage). Do NOT:
If you continue past this point, you violate the loop contract. The PostToolUse hook will surface a reminder; ignoring it compounds the violation.
npx claudepluginhub multiroute/bugfix --plugin bugfixGuides creation, editing, and verification of skills for AI coding agents using test-driven development with subagent scenarios. Use when authoring or debugging skills.