| name | bugfix |
| description | End-to-end bug fix workflow — report, analyze, fix, verify, ship (PR + merge + deploy + knowledge capture) |
| argument-hint | Bug description or BUG-xxx ID |
/bugfix — Bug Fix Workflow
You are fixing a bug using a streamlined workflow that skips the full spec ceremony but follows the same deployment strategy as a feature: changes land via PR, ride the project's CI/CD pipeline (staging-first if the project has one), and aren't marked resolved until every declared deploy target is confirmed.
Ethos
!sh .adlc/partials/ethos-include.sh 2>/dev/null || sh ~/.claude/skills/partials/ethos-include.sh
Context
- Project config: !
cat .adlc/config.yml 2>/dev/null || echo "No config — single-repo legacy mode"
- Bug template: !
cat .adlc/templates/bug-template.md 2>/dev/null || cat ~/.claude/skills/templates/bug-template.md 2>/dev/null || echo "No bug template found"
- Conventions: !
cat .adlc/context/conventions.md 2>/dev/null || echo "No conventions found"
- Existing bugs: !
ls .adlc/bugs/ 2>/dev/null || echo "No bugs directory found"
Input
Bug report: $ARGUMENTS
Prerequisites
Before proceeding, verify that .adlc/bugs/ exists. If it doesn't, stop and tell the user: "The .adlc/ structure hasn't been initialized. Run /init first."
Instructions
Phase 1: Report
- If given a bug description (not a BUG ID), create a bug report:
- Determine the next BUG ID using the global atomic counter file
~/.claude/.global-next-bug (shared across all repos for unique IDs, mirroring the REQ counter — see LESSON-004). The counter is now a cache, not the authority — the remote is the source of truth (REQ-518): allocation derives the remote high-water, takes max(remote, local) + 1, and fast-forwards the local counter, all inside the existing mkdir lock. Allocate via the shared partials/id-alloc.sh helper (BR-5 — the lock block + its REQ-416/LESSON-014 rationale live in the partial). Source it and call adlc_alloc_id in the same fenced block (the cross-fence-fn rule — see conventions.md "Bash in skills"):
. .adlc/partials/id-alloc.sh 2>/dev/null || . ~/.claude/skills/partials/id-alloc.sh
BUG_NUM=$(adlc_alloc_id bug)
[ -n "$BUG_NUM" ] || { echo "ERROR: failed to allocate BUG number — aborting before writing malformed bug report" >&2; exit 1; }
adlc_alloc_id bug handles the absent-counter bootstrap scan internally (highest BUG-xxx under $ADLC_REPOS_ROOT; bug reports are .md files so the scan uses -type f), the mkdir lock that serializes concurrent sessions, and the remote high-water max. Single-machine behavior is unchanged when the remote has no higher allocation (BR-7).
Note: the legacy per-repo .adlc/.next-bug counter is deprecated and no longer consulted — existing files can be left in place but should not be read or written.
- Pre-push recheck (BR-4, BR-8). Before the bug file is committed on a branch for push, re-verify
BUG-<id> against the remote — a colleague on another machine may have pushed the same id since allocation. Source partials/id-recheck.sh and call adlc_recheck_id in the same fenced block; a collision halts with the renumber instruction rather than pushing a duplicate:
. .adlc/partials/id-recheck.sh 2>/dev/null || . ~/.claude/skills/partials/id-recheck.sh
BUG_ID=$(printf 'BUG-%03d' "$BUG_NUM")
if ! adlc_recheck_id bug "$BUG_ID"; then
echo "Halting: $BUG_ID collides on the remote — renumber before pushing (see message above)." >&2
exit 1
fi
- Create
.adlc/bugs/BUG-xxx-slug.md (always in the current repo — this becomes the "primary" for the bug) using the template from .adlc/templates/bug-template.md
- Fill in: description, reproduction steps (if known), expected vs actual behavior, environment
- Set status to
open, severity based on impact
- Cross-repo: if
.adlc/config.yml declares siblings AND the bug's fix likely lives in a sibling (e.g., a frontend symptom whose root cause is in a backend repo), add a repo: <sibling-id> field to the bug frontmatter. If the fix spans multiple repos, add a touched_repos: [<id>, <id>] field. The repo: field determines where Phase 3's commit and Phase 4's PR land.
- If given a BUG ID, read the existing bug report — note any
repo: or touched_repos: field for routing.
Phase 2: Analyze
- Launch Explore agents to trace the bug:
- Search for relevant code paths based on the bug description
- Trace the execution flow that triggers the bug
- Identify the root cause (not just symptoms)
- Read the identified files to understand the context
- Document the root cause in the bug report's "Root Cause" section
- Validate the analysis:
- Re-read the affected code paths to confirm the root cause is correct
- Check for secondary issues or edge cases related to the bug
- Adjust the root cause and fix approach if validation reveals inaccuracies
- Update the bug report with the validated findings
Phase 3: Fix
- Determine target repo: if the bug's frontmatter has
repo: and it names a sibling (not this repo), cd into that sibling's path from .adlc/config.yml and do all fix work there. For touched_repos: [...], cd into each in turn — one commit per repo, on a shared branch name. Otherwise fix in the current repo.
- Proceed directly with the validated fix approach — do not pause for user confirmation
- Implement the fix following project conventions
- Ensure the fix addresses the root cause, not just symptoms
- Update related test files if the fix changes behavior
- Track progress with TodoWrite
Phase 4: Verify
- Run the test suite:
npm test (or appropriate test command)
- If tests fail, fix and re-run
- Update the bug report (do NOT mark
resolved yet — that happens in Phase 6 after the fix is merged and deployed):
- Leave status as
open (or set to in-review if your project uses that value)
- Fill in "Resolution" section with what was changed and why
- Fill in "Files Changed" section with specific file paths
- Update the
updated date
- Present an interim summary:
- Root cause
- What was fixed
- Files changed
- Test results
- Then continue to Phase 5
Phase 5: Ship — Create Pull Request(s)
For each touched repo (just the current repo in single-repo mode; each entry in touched_repos: in cross-repo mode):
- Push the fix branch:
git -C <worktree> push -u origin fix/bug-xxx-slug
- Create the PR with
adlc_forge_pr_create (source partials/forge.sh in the same fence; run from inside the worktree, or pass -R <owner/repo>). All PR ops route through the forge adapter, never direct gh (REQ-520 BR-1). In cross-repo mode, create the primary repo's PR last so its body can link every sibling.
- After all sibling PRs exist, edit each one (
adlc_forge_pr_edit <prUrl> --body ...) to fill in the Related PRs section.
- Wait for CI to pass on every PR:
gh pr checks <prUrl>. If CI fails, diagnose and re-push — never bypass with --no-verify or admin-merge.
- Report all PR URLs to the user, grouped by repo.
Phase 6: Wrapup — Merge, Deploy, Knowledge Capture
This is the equivalent of /proceed's Phase 8 / /wrapup steps, condensed for bugs.
Step 1 — Merge each PR.
- Verify the PR is mergeable:
adlc_forge_pr_view <prUrl> --json mergeable,mergeStateStatus should report MERGEABLE (on GitHub; ADO normalizes via pr_view). If main has advanced, rebase the fix branch onto origin/main, force-push with lease, and wait for CI to re-pass.
- Merge with squash + branch delete:
adlc_forge_pr_merge <prUrl> --squash --delete-branch. In cross-repo mode, walk touched_repos: order (or merge_order: from .adlc/config.yml if not specified on the bug).
Step 2 — Confirm deploys (this is the staging-first gate when the project has one — same model as features).
Skip this step entirely if the project doesn't deploy via Cloud Run (i.e., stack.backends in .adlc/config.yml doesn't include cloud-run and there's no gcp: block).
Otherwise, for each touched service that has a services: entry in .adlc/config.yml, look up gcp.staging_project and gcp.production_project from the config and confirm both:
gcloud run services describe <service> \
--project=<gcp.staging_project from config> \
--region=<services[<id>].region or gcp.default_region> \
--format="value(status.latestReadyRevisionName,status.traffic[0].revisionName)"
gcloud run services describe <service> \
--project=<gcp.production_project from config> \
--region=<services[<id>].region or gcp.default_region> \
--format="value(status.latestReadyRevisionName,status.traffic[0].revisionName)"
Confirm the merge SHA's revision is serving 100% traffic in each. If gcp.production_project is omitted (no separate prod project), only confirm staging.
If staging deployed but production has NOT yet been promoted, wait — the pipeline runs them sequentially. If either fails, surface to the user with the failed deploy log link before claiming the bug resolved.
iOS deploy (only when stack.frontends in .adlc/config.yml includes ios AND the fix touched the iOS repo):
- Read
ios.deploy_targets, ios.derived_data_clean, and ios.deploy_command from .adlc/config.yml.
- If
ios.derived_data_clean is true: rm -rf ~/Library/Developer/Xcode/DerivedData/*
- From the iOS repo's worktree, run
<ios.deploy_command> and deploy to every device in ios.deploy_targets — never skip one. Don't leave this as a follow-up for the user.
If stack.frontends doesn't include ios, skip this section entirely.
Step 3 — Update the bug report.
- Set status to
resolved
- Update the
updated date
- Confirm Resolution and Files Changed sections are filled in (from Phase 4)
- Add a Deployment section noting the staging + production revisions
Step 4 — Capture knowledge (NEVER skip — per memory feedback_wrapup_knowledge_capture.md).
Evaluate honestly: did this bug reveal something a future implementer should know?
- A surprising failure mode (race condition, schema mismatch, mocked-vs-real divergence, etc.)?
- A pattern or anti-pattern worth recording?
- A check that would have caught this earlier?
- An assumption from a prior REQ that turned out false?
If yes, write a lesson to .adlc/knowledge/lessons/LESSON-xxx-slug.md using the global atomic counter ~/.claude/.global-next-lesson (shared across all repos for unique IDs, mirroring the REQ/BUG counters — see LESSON-004). The counter is now a cache, not the authority — the remote is the source of truth (REQ-518): allocation derives the remote high-water, takes max(remote, local) + 1, and fast-forwards the local counter, all inside the shared mkdir-lock (~/.claude/.global-next-lesson.lock.d, shared with /wrapup so concurrent /bugfix and /wrapup runs mutually exclude). Allocate via the shared partials/id-alloc.sh helper (BR-5 — the lock block + its LESSON-014 symlink pre-check live in the partial). Source it and call adlc_alloc_id in the same fenced block (the cross-fence-fn rule — see conventions.md "Bash in skills"):
. .adlc/partials/id-alloc.sh 2>/dev/null || . ~/.claude/skills/partials/id-alloc.sh
LESSON_NUM=$(adlc_alloc_id lesson)
[ -n "$LESSON_NUM" ] || { echo "ERROR: failed to allocate LESSON number — aborting before writing malformed lesson" >&2; exit 1; }
adlc_alloc_id lesson handles the absent-counter bootstrap scan internally (highest LESSON-xxx under $ADLC_REPOS_ROOT; lessons are .md files so the scan uses -type f), the shared mkdir lock, and the remote high-water max. Single-machine behavior is unchanged when the remote has no higher allocation (BR-7). Note: the legacy per-repo .adlc/.next-lesson counter is deprecated and no longer consulted — existing files can be left in place but should not be read or written.
Pre-push recheck (BR-4, BR-8). Before the lesson file is committed on a branch for push, re-verify LESSON-<id> against the remote — a colleague on another machine may have pushed the same id since allocation. Source partials/id-recheck.sh and call adlc_recheck_id in the same fenced block; a collision halts with the renumber instruction rather than pushing a duplicate:
. .adlc/partials/id-recheck.sh 2>/dev/null || . ~/.claude/skills/partials/id-recheck.sh
LESSON_ID=$(printf 'LESSON-%03d' "$LESSON_NUM")
if ! adlc_recheck_id lesson "$LESSON_ID"; then
echo "Halting: $LESSON_ID collides on the remote — renumber before pushing (see message above)." >&2
exit 1
fi
Use the lesson template (.adlc/templates/lesson-template.md, fall back to ~/.claude/skills/templates/lesson-template.md). Filename format is LESSON-xxx-slug.md only — no date prefixes, no bare-numeric prefixes. Include domain, component, and tags so future runs of /spec, /architect, /reflect, and /review can filter by relevance.
If the bug genuinely produced no useful lesson (one-line typo, etc.), say so explicitly in the final summary — don't silently skip.
Step 5 — Clean up.
- Switch the local checkout to main and pull:
git -C <main-worktree> checkout main && git -C <main-worktree> pull
- If the fix was done in a separate worktree, remove it:
git -C <main-worktree> worktree remove <fix-worktree-path>
- If the fix branch still exists locally after squash-merge, delete it:
git branch -D fix/bug-xxx-slug
- Prune remote-tracking refs:
git fetch --prune
Step 6 — Final ship summary.
## BUG-xxx: Bug Title — Resolved
**Severity**: <severity>
**PR(s)**: #nn (and siblings if cross-repo)
**Merged**: YYYY-MM-DD
### Root cause
- 1-2 lines
### Fix
- 1-2 lines
### Deployment
- Staging: <service> revision <hash> @ 100% traffic
- Production: <service> revision <hash> @ 100% traffic
- iOS: deployed to <list of ios.deploy_targets from config> (or "n/a — backend-only fix")
### Lessons captured
- `.adlc/knowledge/lessons/LESSON-xxx-slug.md` — one-line hook
(or "None — fix was straightforward and revealed no new pattern")
Branch Naming
Use fix/bug-xxx-slug for the branch name. In cross-repo bugs, use the same branch name in every touched repo so PRs can be linked visually.
Commit Message Format
fix(BUG-xxx): short description of the fix
Cross-Repo Bugs (brief)
When a bug's fix spans repos (via touched_repos: in the bug frontmatter):
- The bug report itself always lives in the repo
/bugfix was invoked from (the "primary" for this bug).
- Phase 3 makes one commit per touched repo, each on a branch with the same name (
fix/bug-xxx-slug).
- Phase 5 opens one PR per touched repo and cross-links them (primary PR's body is created last so it can reference every sibling URL).
- Phase 6 merges in the order the repos are listed in
touched_repos:. If the bug report doesn't specify an order, use the merge_order from .adlc/config.yml.
- If this gets complicated (more than 2 touched repos, or ordering matters), consider promoting the bug into a full REQ and using
/proceed instead.