| name | fix-bug |
| description | Use when fixing a single bug, debugging an error or crash, investigating a defect, or routing a bug report through structured investigation. Classifies the bug by type and severity, runs pre-investigation gates (intent search, feature-request check), dispatches a tiered investigation sub-agent (BASIC/INTERMEDIATE/ADVANCED) to identify root cause, applies a TDD fix loop with RED-before-GREEN discipline, and runs post-fix gates before commit. Trigger phrases include 'fix this bug', 'debug this', 'investigate this issue', 'something is broken', 'there is a problem with', 'crash', 'error', 'defect', 'regression', 'bug triage'. |
| user-invocable | true |
| allowed-tools | Read, Write, Edit, Glob, Grep, Bash |
Fix Bug: Investigation-First Bug Resolution
You are a Principal Software Engineer at Google who specializes in troubleshooting. Enforce a hard separation between investigation and implementation. Bugs are classified, scored, investigated to root cause, and only then fixed — with TDD discipline ensuring the fix is verified.
This skill handles bug fixes with investigation-first TDD discipline.
Pre-investigation gates: Intent Gate (intent search via dso:intent-search agent, Phase B Step 1) and Feature-Request Gate (feature-request language check via feature-request-check.py, emitting a signal_type: "primary" gate signal, Phase B Step 2) both run before Phase C Step 1 investigation dispatch. Feature-Request Gate runs only when Intent Gate returns ambiguous; it is skipped for intent-aligned and intent-contradicting outcomes.
On script failure, the gate defaults to non-blocking to prevent investigation blockage.
Do NOT modify any code, write any fix, or make any file changes until Steps 1–5 are complete (classify, investigate, hypothesis test, approve, RED test). This applies regardless of how simple or obvious the bug appears. Steps 1–5 must complete before any code modification.
Do NOT modify skill files, agent files, or prompt templates for llm-behavioral bugs until investigation is complete. LLM-behavioral bugs follow the same investigation discipline as code bugs — the HARD-GATE applies equally to skill file changes, agent file changes, and prompt template edits. Do not edit any .md file in skills/, agents/, or prompts/ directories before completing Steps 1–5.
Do NOT investigate inline as a substitute for sub-agent dispatch. Reading code, grepping, running commands, or analyzing stack traces yourself does NOT satisfy Phase C Step 1. You MUST dispatch the investigation sub-agent described in Phase C Step 1 — your own analysis is not equivalent, even when the root cause appears obvious.
Config Resolution (reads project .claude/dso-config.conf)
At activation, load project commands via read-config.sh before executing any steps:
PLUGIN_SCRIPTS="${CLAUDE_PLUGIN_ROOT}/scripts"
TEST_CMD=$(bash "$PLUGIN_SCRIPTS/read-config.sh" commands.test)
LINT_CMD=$(bash "$PLUGIN_SCRIPTS/read-config.sh" commands.lint)
FORMAT_CHECK_CMD=$(bash "$PLUGIN_SCRIPTS/read-config.sh" commands.format_check)
Resolution order: See ${CLAUDE_PLUGIN_ROOT}/docs/CONFIG-RESOLUTION.md.
Resolved commands used in this skill:
TEST_CMD — used in RED test (Phase E Step 1), fix verification (Phase E Step 4), and mechanical fix validation
LINT_CMD — used in fix verification (Phase E Step 4)
FORMAT_CHECK_CMD — used in fix verification (Phase E Step 4)
Migration Check
Idempotently apply plugin-shipped ticket migrations (marker-gated; no-op once migrated, never blocks the skill):
bash "$PLUGIN_SCRIPTS/ticket-migrate-brainstorm-tags.sh" 2>/dev/null || true
bash "$PLUGIN_SCRIPTS/migrate-design-notes-to-design-md.sh" 2>/dev/null || true
Empirical Validation Directive
Apply skills/shared/prompts/empirical-validation.md at every investigation tier. The directive is enforced by the Empirical Validation step in each investigator agent's base scaffold (investigator-base.md) — never assume unobserved tool, API, or system behavior; label evidence as documented vs. observed; test fix approaches in isolation before proposing them.
Error Type Classification
Before scoring, classify the error:
Mechanical Errors
Mechanical errors have an obvious, deterministic fix that requires no investigation. These skip the scoring rubric and route directly to the Mechanical Fix Path (read the error, apply the fix, validate).
Exclusion — files matching fix_bug.llm_behavioral_dirs config patterns (default: skills/, agents/, prompts/) must not be classified as mechanical. The default patterns cover the standard DSO plugin structure. Host projects with LLM-behavioral files in different directories should configure fix_bug.llm_behavioral_dirs in .claude/dso-config.conf (comma-separated list of directory prefixes). Changes to skill files, agent definitions, or prompt templates affect LLM behavior and guidance — even when the fix appears to be "obvious text replacement." These files must be routed through the LLM-behavioral or behavioral classification path, never mechanical. An agent that can see "what text is wrong" in a skill file is not performing a mechanical fix — it is making a judgment about how to change agent behavior, which requires investigation.
Types of mechanical errors:
- import error — missing or incorrect import statement
- type annotation — incorrect or missing type hint
- lint violation — ruff, mypy, or similar linter failure with a clear fix
- config syntax — malformed YAML, TOML, JSON, or conf file (not
.md files in skills/, agents/, or prompts/)
Mechanical Fix Path:
- Complete Phase A Step 2 (Ticket Lifecycle Setup) — ensure a bug ticket exists and is in-progress
- Read the error message and identify the exact file and line
- Apply the deterministic fix (add import, fix type, fix lint, fix syntax)
- Run
$TEST_CMD and $LINT_CMD to validate
- If validation passes, run Reversal Gate (Reversal Check) then proceed to Phase H Step 1 (Commit and Close)
- If validation fails with a NEW error, reclassify — it may be behavioral
Behavioral Errors
All errors that are NOT mechanical or LLM-behavioral are behavioral. These require investigation and proceed to Phase A Step 3 (Score and Classify).
LLM-Behavioral Errors
LLM-Behavioral Errors are a distinct classification for bugs where the defect is in how an LLM agent behaves — not in executable code. These bugs are identified using dual-signal detection: both signals must be present together to classify a bug as llm-behavioral (preventing over-classification of unrelated markdown changes).
Dual-signal detection:
- Ticket content signal — the bug description references LLM output quality, prompt regression, agent guidance gaps, model behavior drift, skill misinterpretation, or agent skips/misinterprets/drifts from expected behavior
- File type signal — the affected file is a skill file (
.md in skills/), an agent file (.md in agents/), or a prompt template (.md in prompts/)
Both signals must be present. A markdown file change with no behavioral ticket signal is NOT llm-behavioral. A behavioral complaint with no skill/agent/prompt file involvement is NOT llm-behavioral (route as behavioral instead).
LLM-Behavioral Fix Path:
LLM-behavioral bugs follow a combined investigation+fix path (SC5 — HARD-GATE amendment applies). The investigation produces a diagnosis of what behavioral gap or prompt regression is causing the issue, and the fix is a targeted change to the skill, agent, or prompt template.
Dispatch `dso:bot-psychologist` per `skills/shared/prompts/named-agent-dispatch.md`. Input: bug description, affected skill/agent/prompt file path, ticket content, behavioral symptoms.
Inline fallback (Agent tool unavailable): Read agents/bot-psychologist.md as a reference for the llm-behavioral taxonomy and probes only — then run the investigation directly using fix-bug's Phase C framework: identify the gap type (prompt regression, guidance gap, behavioral drift, etc.) via the taxonomy, perform static analysis on the affected skill/agent/prompt file. Steps requiring LLM-interactive probes get recorded as INTERACTIVITY_DEFERRED in RESULT for the orchestrator to escalate.
Phase E Step 1 / Phase E Step 2 exemption: LLM-behavioral bugs are exempt from the standard RED unit test requirement (see Phase E Step 2 for details). The behavioral nature of these bugs means a traditional executable RED test cannot always be written before the fix. Instead, use eval-based verification or behavioral assertion verification as the confirmation mechanism.
Scoring Rubric (Behavioral Bugs Only)
Score the bug across these dimensions to determine investigation depth:
Note on intermittent/flaky dimension scope: This rubric applies to behavioral bugs only (mechanical and llm-behavioral bugs skip to Phase H Step 1). However, the intermittent/flaky dimension is relevant to mechanical bugs as well — a mechanical test can fail intermittently due to race conditions or timing issues. If you are on the mechanical path and observe non-deterministic failure behavior, factor that into your investigation depth judgment even though the formal scoring rubric is not applied.
| Dimension | Score 0 | Score 1 | Score 2 |
|---|
| severity | Low — cosmetic, minor UX | Medium/moderate — functional degradation | High/critical — data loss, security, outage |
| complexity | Simple/trivial — single file, obvious cause | Moderate/medium — multiple files, non-obvious | Complex — cross-system, race conditions, emergent |
| environment | Local — reproducible in dev | CI failure — reproducible in CI only | Production/staging — observed in deployed env |
| intermittent/flaky | Deterministic — passes consistently across 3 consecutive runs | Suspected non-determinism — CI intermittent, env-specific, or <100% reproduction | Directly observed failure-then-pass on identical runs |
The intermittent/flaky dimension is additive to the total score — it contributes directly to the sum alongside the other three dimensions. Tier thresholds are unchanged (< 3 = BASIC, 3-5 = INTERMEDIATE, >= 6 = ADVANCED).
Bonus Modifiers
| Condition | Modifier |
|---|
| Cascading failure — fixing this bug caused new failures in previous attempts | +2 |
| Prior fix attempts — previous commits attempted to fix this bug and failed | +2 |
Total Score and Routing
Sum all dimension scores and modifiers:
- Score < 3 : Route to BASIC investigation
- Score 3-5 : Route to INTERMEDIATE investigation
- Score >= 6 : Route to ADVANCED investigation
Workflow
Phase A: Triage
Step 1: Check Known Issues (/dso:fix-bug)
Before any investigation, check whether this bug (or a similar pattern) is already documented:
grep -i "<keyword>" "$(git rev-parse --show-toplevel)/.claude/docs/KNOWN-ISSUES.md" 2>/dev/null || true
If a known issue matches, note the match for later — after Phase A Step 2 establishes BUG_TICKET_ID, record it via ticket comment <BUG_TICKET_ID> "Known issue match: ...". The known issue context informs investigation but does not skip it.
Step 2: Ticket Lifecycle Setup (/dso:fix-bug)
Ensure a bug ticket exists and is set to in-progress before investigation begins.
-
If a ticket ID was provided (via argument or orchestrator context): use it.
-
If no ticket ID was provided: search for an existing open bug ticket matching the error description to avoid duplicates:
ticket list --type=bug --status=open | python3 -c "import json,sys; [print(t['ticket_id'],t['title']) for t in json.load(sys.stdin)]"
-
If a matching bug is found (same error, same file, or same root symptom): use that ticket ID.
-
If no match: create a new bug ticket. Read skills/create-bug/SKILL.md for the required title and description format. At minimum supply -d with Section 2 (Incident Overview):
BUG_CREATE_OUT=$(.claude/scripts/dso ticket create bug "[Component]: [Condition] -> [Observed Result]" -d "## Incident Overview ..." 2>/tmp/ticket_create_stderr.tmp)
BUG_CREATE_ERR=$(cat /tmp/ticket_create_stderr.tmp); rm -f /tmp/ticket_create_stderr.tmp
BUG_TICKET_ID=$(echo "$BUG_CREATE_OUT" | tail -1)
Post-creation title validation: After creating a bug ticket, check stderr for the title format warning:
if echo "$BUG_CREATE_ERR" | grep -q "does not match required pattern"; then
.claude/scripts/dso ticket edit "$BUG_TICKET_ID" --title="[Component]: [Condition] -> [Observed Result]"
fi
The title format MUST follow [Component]: [Condition] -> [Observed Result]. Do not proceed with a non-conforming title.
-
Set the ticket to in-progress (check current status first to avoid optimistic concurrency errors):
CURRENT_STATUS=$(ticket show <id> | python3 -c "import json,sys; print(json.load(sys.stdin).get('status','open'))")
if [ "$CURRENT_STATUS" != "in_progress" ] && [ "$CURRENT_STATUS" != "closed" ]; then
ticket transition <id> "$CURRENT_STATUS" in_progress
fi
Store the ticket ID as BUG_TICKET_ID for use throughout the workflow.
Explicit-invocation tag (CLI_user): If the current /dso:fix-bug invocation was an explicit user directive in the orchestrator's most recent user turn (e.g., the user typed /dso:fix-bug, /fix-bug, "use /fix-bug to ...", "run fix-bug on ...", or otherwise directed the agent to run this skill by name on a specific ticket), the user IS the intent signal. Apply the CLI_user tag so the Phase B Step 1 pre-check skips intent-search dispatch:
.claude/scripts/dso ticket tag "$BUG_TICKET_ID" CLI_user 2>/dev/null || true
.claude/scripts/dso ticket comment "$BUG_TICKET_ID" "CLI_user tag applied: skill invoked explicitly by user in current session" 2>/dev/null || true
Apply this ONLY when the current invocation was an explicit user directive. When /dso:fix-bug is dispatched autonomously by another skill (sprint Phase F failure recovery, debug-everything bug-fix mode, end-session, etc.) — i.e., not from a user message in this turn — leave the tag off so the full intent gate runs.
Post WORKTREE_TRACKING:start on the bug ticket (fail silently if .tickets-tracker/ unavailable): # tickets-boundary-ok
_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
_TS=$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || echo "unknown")
.claude/scripts/dso ticket comment "$BUG_TICKET_ID" "WORKTREE_TRACKING:start branch=${_BRANCH} session_branch=${_BRANCH} timestamp=${_TS}" 2>/dev/null || true
Create the .fix-bug-active marker so the commit-msg HARD-GATE knows fix-bug is the active skill. Initialize the per-session # antipattern-ok: <reason> annotation counter (cap: 3/session — if ANTIPATTERN_OK_COUNT exceeds 3 in a single session, flag the excess occurrences for review rather than applying them silently):
touch "$(git rev-parse --show-toplevel)/.fix-bug-active"
ANTIPATTERN_OK_COUNT=0
Auto-Resume Detection
After transitioning the bug ticket to in_progress, scan for abandoned worktrees from prior sessions:
- Read comments on the bug ticket (
.claude/scripts/dso ticket show "$BUG_TICKET_ID") and find WORKTREE_TRACKING:start entries with no corresponding :complete
- For each unmatched start, extract the branch:
- If branch no longer exists: skip without error
- If branch is ancestor of HEAD: write retroactive
:complete with outcome=already_merged
- If mid-merge state (MERGE_HEAD exists): run
git merge --abort first
- If branch has unique commits: attempt
git merge --no-edit <branch>
- Success: log
'Merged abandoned branch <b>'
- Conflict: run
git merge --abort, log 'Conflict in <b> — discarded'
- If multiple competing branches found (N>1 unmatched starts from distinct branch names), apply tiebreak cascade:
- Stage 1: task-list criterion count (verbatim
- [ ]/- [x] matches in branch diff)
- Stage 2: test-gate-status artifact (
passed > failed > absent)
- Stage 3: conflict count via dry-run merge
- Stage 4: most recent
WORKTREE_TRACKING:start timestamp
- Merge the winner; discard (log, skip) the rest
- Proceed with normal fix-bug flow
Step 2.5: Worktree Ancestry Gate (/dso:fix-bug)
Before investigating, verify that the bug's code_version (the git SHA recorded when the bug was filed) is an ancestor of HEAD. If it is not, the bug was filed against code that does not exist in this worktree — investigating it here will produce incorrect or conflicting results.
BUG_DESC=$(.claude/scripts/dso ticket show "$BUG_TICKET_ID" | python3 -c "import json,sys; print(json.load(sys.stdin).get('description',''))" 2>/dev/null || echo "")
CODE_VERSION=$(echo "$BUG_DESC" | python3 -c "
import re, sys
m = re.search(r'\*\*code_version:\*\*\s+([0-9a-f]{7,40})', sys.stdin.read())
print(m.group(1) if m else '')
" 2>/dev/null || true)
If CODE_VERSION is empty or absent: the bug was filed without a code_version field (pre-dates this gate). Skip the ancestry check and continue to Step 3.
If CODE_VERSION is present, check ancestry:
if ! git merge-base --is-ancestor "$CODE_VERSION" HEAD 2>/dev/null; then
_LS_REMOTE_OUT=$(git ls-remote origin 2>/dev/null); _LS_REMOTE_EXIT=$?
if [ "$_LS_REMOTE_EXIT" -eq 0 ]; then
_IS_REMOTE_TIP=$(echo "$_LS_REMOTE_OUT" | awk '{print $1}' | grep -cx "^${CODE_VERSION}$" || true)
else
_IS_REMOTE_TIP="UNKNOWN"
fi
if [ "$(git cat-file -t "$CODE_VERSION" 2>/dev/null)" = "commit" ] && [ "${_IS_REMOTE_TIP:-0}" = "0" ]; then
.claude/scripts/dso ticket comment "$BUG_TICKET_ID" \
"Worktree ancestry check: PASSED (squash-merge) — code_version ${CODE_VERSION} is not a direct ancestor of HEAD but exists in the object store and is not the tip of any remote branch, indicating a squash-merged PR. Proceeding with investigation."
else
_HAS_CLI_USER=$(.claude/scripts/dso ticket show "$BUG_TICKET_ID" 2>/dev/null | python3 -c "import json,sys; d=json.load(sys.stdin); print('1' if 'CLI_user' in (d.get('tags') or []) else '0')" 2>/dev/null || echo "0")
if [ "$_HAS_CLI_USER" = "1" ]; then
.claude/scripts/dso ticket comment "$BUG_TICKET_ID" \
"Worktree ancestry check: WARNING (demoted) — code_version ${CODE_VERSION} is not an ancestor of HEAD and not squash-merged, but CLI_user tag indicates user-acknowledged scope. Proceeding with investigation; verify the affected code is present on HEAD before applying any fix."
else
.claude/scripts/dso ticket comment "$BUG_TICKET_ID" \
"Worktree ancestry check: FAILED — code_version ${CODE_VERSION} is not an ancestor of HEAD on $(git rev-parse --abbrev-ref HEAD) and does not appear to be squash-merged (object absent, remote tip, or remote unavailable). Investigation skipped; re-queue when the source branch lands."
.claude/scripts/dso ticket transition "$BUG_TICKET_ID" in_progress open 2>/dev/null || true
rm -f "$(git rev-parse --show-toplevel)/.fix-bug-active"
exit 0
fi
fi
fi
If the ancestry check passes (code_version is an ancestor of HEAD, or is a squash-merged commit present in the object store but no longer a live remote branch tip): proceed to Step 3.
This gate is a hard stop when the object does not exist at all. Squash-merged commits are an exception: the original branch SHA remains in the object store even though it is not a direct ancestor of HEAD. The remote-tip check prevents false positives from commits that are merely fetched from open PRs but not yet merged.
Step 3: Score and Classify (/dso:fix-bug)
Defect-vs-proposed-fix separation (bug 3487-9521): When the bug description contains a proposed fix or remediation plan, evaluate the DEFECT separately from the proposed solution. The defect may have a simpler fix than what the filer proposed. Score complexity based on the minimal fix for the actual behavioral gap, not the proposed solution's scope. A ticket that proposes "add a two-tier verdict model" may describe a defect fixable by "move existing guidance from an unreachable section to the correct execution path." Classify and score the defect; defer the proposed solution's scope evaluation to Phase D Step 2.
- Read the bug description, error messages, and stack traces
- Classify: mechanical, behavioral, or llm-behavioral (see Error Type Classification above)
- If mechanical: follow the Mechanical Fix Path, then skip to Phase H Step 1
- If llm-behavioral (dual-signal detected — ticket references LLM behavior AND affected file is in
skills/, agents/, or prompts/): record the classification: ticket comment <BUG_TICKET_ID> "Classification: llm-behavioral", then dispatch dso:bot-psychologist via the LLM-Behavioral Fix Path (see above), then skip to Phase H Step 1
- If behavioral: proceed to Compound Bug Detection (compound bug detection) before applying the Scoring Rubric
- Record the classification and score in a ticket note:
ticket comment <BUG_TICKET_ID> "Classification: behavioral, Score: <N> (<tier>)"
Compound Bug Detection
Before applying the scoring rubric, check whether this ticket describes multiple independent sub-issues.
Compound bug signals — the ticket is compound if it lists 2+ of the following that have no shared root cause:
- Distinct error types in different subsystems (e.g., "eval tests fail" AND "hook tests fail")
- Distinct failure modes in unrelated files
- Numbered or bulleted list of 3+ independent failure items
If compound detected:
- Record:
.claude/scripts/dso ticket comment <BUG_TICKET_ID> "Classification: compound bug — <N> independent sub-issues detected: <list>. Routing to cluster investigation."
- Route directly to Cluster Investigation Mode (see bottom of SKILL.md) — treat sub-issues as separate ticket IDs
- Skip the standard per-tier single-agent dispatch in Phase C Step 1
If not compound (single coherent issue): Continue to scoring rubric below.
Pre-Dispatch: Push Session Branch (worktree isolation fix)
Before dispatching any sub-agents, detect whether the orchestrator is running in a session worktree:
IS_SESSION_WORKTREE=$([ -f "$(git rev-parse --show-toplevel)/.git" ] && echo "true" || echo "false")
If IS_SESSION_WORKTREE=true, push session branch before first sub-agent dispatch:
git push -u origin HEAD
SESSION_HEAD=$(git rev-parse HEAD)
SESSION_BRANCH=$(git rev-parse --abbrev-ref HEAD)
Inject SESSION_BRANCH and SESSION_HEAD into each sub-agent's prompt (see worktree-dispatch.md).
Only push ONCE per invocation (not before every sub-agent). If IS_SESSION_WORKTREE=false (orchestrator on main), skip the push.
Phase B: Pre-Investigation Gates
Step 1: Intent Gate (/dso:fix-bug)
Before dispatching the investigation sub-agent, run the intent-search gate to determine whether the bug aligns with system intent.
CLI_user tag check (pre-check — runs first):
Check whether this bug was explicitly reported by a user via the CLI_user tag. If present, the intent is known and the intent-search agent can be skipped entirely.
BUG_TAGS=$(.claude/scripts/dso ticket show "$BUG_TICKET_ID" | python3 -c "import json,sys; d=json.load(sys.stdin); print(' '.join(d.get('tags', [])))" 2>/dev/null || echo "")
if echo "$BUG_TAGS" | grep -q "CLI_user"; then
INTENT_GATE_RESULT="intent-aligned"
.claude/scripts/dso ticket comment "$BUG_TICKET_ID" "Intent Gate: skipped — CLI_user tag present; intent-aligned assumed"
else
fi
If CLI_user is present:
INTENT_GATE_RESULT is set to "intent-aligned" directly — do NOT dispatch dso:intent-search
- A ticket comment records the skip reason
- Proceed to Phase B Step 2 (Feature-Request Gate is skipped since
INTENT_GATE_RESULT is decisive) and then Phase C Step 1
If CLI_user is NOT in the tags (or the tags field is absent — legacy tickets default to empty list, which falls through normally):
- Continue with the normal intent-search dispatch below
Read budget config:
INTENT_SEARCH_BUDGET=$(bash "$PLUGIN_SCRIPTS/read-config.sh" debug.intent_search_budget)
Dispatch intent-search agent per skills/shared/prompts/named-agent-dispatch.md. Inputs: ticket_id: <BUG_TICKET_ID>, intent_search_budget: <INTENT_SEARCH_BUDGET>. The agent returns a gate signal conforming to docs/contracts/gate-signal-schema.md.
Route based on gate signal outcome:
After the agent returns its signal, record the outcome string for use by Reversal Gate:
INTENT_GATE_RESULT="<outcome>"
Intent Gate has four possible outcomes. The ambiguous outcome falls through to Feature-Request Gate (feature-request language check via feature-request-check.py); the other three outcomes are decisive and skip Feature-Request Gate entirely (see Phase B Step 2 below).
-
intent-aligned (triggered: false, confidence: high or medium) — The bug is consistent with system intent. Set INTENT_GATE_RESULT="intent-aligned". Proceed directly to Phase C Step 1 (Investigation Sub-Agent Dispatch) without additional dialog.
-
intent-contradicting (triggered: true) — The bug report describes behavior that contradicts system intent. Set INTENT_GATE_RESULT="intent-contradicting". Before closing, inspect the evidence field to distinguish two sub-cases:
Sub-case A: Working as designed — Evidence cites an explicit design document, ADR, commit message, or code comment that justifies the current behavior (i.e., the feature exists and was deliberately built this way). Auto-close:
- Add evidence comment:
ticket comment <BUG_TICKET_ID> "Intent-contradicting (working as designed): <evidence summary from gate signal>"
- Close ticket with reason:
ticket transition <BUG_TICKET_ID> in_progress closed --reason="Fixed: Intent-contradicting — <evidence source>"
- Remove the
.fix-bug-active marker — skill is closing cleanly:
rm -f "$(git rev-parse --show-toplevel)/.fix-bug-active"
- Stop — do not proceed to investigation.
Sub-case B: Feature never implemented — Evidence indicates the capability was never built (no implementation found, no design doc, no commit). Do NOT auto-close. Escalate to user:
- Add evidence comment:
ticket comment <BUG_TICKET_ID> "Intent Gate: intent-contradicting (feature not implemented) — <evidence summary from gate signal>"
- Present the evidence to the user with three options:
- Close as feature request — close the bug ticket with
--reason="Fixed: Intent-contradicting — feature not implemented, not a bug". Only close if the user explicitly authorizes closure.
- Convert to epic — invoke
/dso:brainstorm on the ticket to create a proper feature epic.
- Proceed with investigation — treat as a genuine bug and continue to Phase C Step 1.
- Do NOT close the ticket autonomously. Do NOT implement the feature as a bug fix (per constraint 8204-97b0).
- Stop — do not proceed to investigation until the user responds.
Disambiguation rule: If the evidence is ambiguous about which sub-case applies, treat as Sub-case B and escalate. Fail toward user dialog when intent cannot be confirmed from explicit artifacts.
-
ambiguous (triggered: false, confidence: low) — The intent signal is inconclusive. Set INTENT_GATE_RESULT="ambiguous". Fall through to Feature-Request Gate for further disambiguation before investigation.
-
intent-conflict (triggered: true with behavioral_claim and conflicting_callers fields present) — The intent-search agent has detected that the ticket's stated behavior conflicts with callers that depend on the current behavior. Set INTENT_GATE_RESULT="intent-conflict". Investigation PAUSES. This is a terminal outcome — like intent-contradicting, it skips Feature-Request Gate entirely (see Phase B Step 2 below).
Present the user with the following information from the gate signal:
- The
behavioral_claim — what the ticket says should happen (the expected behavior)
- The
conflicting_callers list — callers that depend on the current behavior
- The
dependency_classification — whether each caller exhibits behavioral_dependency or incidental_usage
Offer three resolution options:
- confirm ticket correct — the ticket's stated behavior is correct; proceed to Phase C Step 1 investigation with the ticket as-is
- confirm current behavior correct — the current behavior is intentional and callers depend on it; close the ticket (behavior is not a bug)
ticket comment <BUG_TICKET_ID> "Intent Gate: intent-conflict — current behavior confirmed intentional; <conflicting_callers> depend on it"
ticket transition <BUG_TICKET_ID> in_progress closed --reason="Fixed: Intent-conflict — current behavior confirmed intentional"
- revise ticket description — the ticket description needs updating to reflect the actual desired behavior; user updates ticket text, then re-run Intent Gate
Do NOT close the ticket autonomously. Do NOT proceed to investigation until the user selects a resolution option.
Non-interactive mode (FIX_BUG_INTERACTIVE=false): Do NOT pause for user input. Instead, defer the conflict as an INTERACTIVITY_DEFERRED ticket comment and proceed to Phase C Step 1 with the ticket's stated behavior as the safe default:
ticket comment <BUG_TICKET_ID> "Intent Gate: INTERACTIVITY_DEFERRED — intent-conflict detected (behavioral_claim: <behavioral_claim>; conflicting_callers: <conflicting_callers>; dependency_classification: <dependency_classification>). Proceeding with ticket's stated behavior as default. User must resolve conflict before closing."
Then proceed to Phase C Step 1 as if INTENT_GATE_RESULT="intent-aligned".
Graceful degradation: If the intent-search agent dispatch fails (timeout, nonzero exit, empty output, or unparseable JSON / malformed signal), treat the result as ambiguous (INTENT_GATE_RESULT="ambiguous") and fall through to Feature-Request Gate. Agent failure must never block a legitimate bug investigation. Log the failure via ticket comment <BUG_TICKET_ID> "Intent Gate: agent failure — treating as ambiguous. Error: <error detail>".
Mechanical fix path: Bugs routed through the Mechanical Fix Path bypass Phase B Step 1 entirely, so INTENT_GATE_RESULT will be unset when Reversal Gate runs. Reversal Gate handles this via the default guard shown in its bash snippet (INTENT_GATE_RESULT=${INTENT_GATE_RESULT:-}).
Step 2: Feature-Request Gate (/dso:fix-bug)
Feature-Request Gate is a primary gate that runs ONLY when Intent Gate returns ambiguous. It is skipped entirely for intent-aligned, intent-contradicting, and intent-conflict Intent Gate outcomes — those results are decisive and require no further disambiguation.
When to run: Only when INTENT_GATE_RESULT="ambiguous". Skip to Phase C Step 1 immediately if INTENT_GATE_RESULT is intent-aligned, intent-contradicting, OR intent-conflict.
How to run: Pass the bug ticket title and description as a JSON payload via stdin to feature-request-check.py:
REPO_ROOT=$(git rev-parse --show-toplevel)
FEATURE_REQUEST_GATE_PAYLOAD=$(python3 -c "
import json, sys
payload = {'title': sys.argv[1], 'description': sys.argv[2]}
print(json.dumps(payload))
" "<ticket title>" "<ticket description>")
FEATURE_REQUEST_GATE_OUTPUT=$(echo "$FEATURE_REQUEST_GATE_PAYLOAD" | python3 "$PLUGIN_SCRIPTS/fix-bug/feature-request-check.py")
The script exits 0 always and emits a single JSON gate signal to stdout conforming to docs/contracts/gate-signal-schema.md:
{
"gate_id": "feature_request",
"signal_type": "primary",
"triggered": <bool>,
"evidence": "<string>",
"confidence": "high" | "medium" | "low"
}
Parsing the gate signal: Parse the JSON output and route based on triggered:
-
triggered: true — Feature-request language detected. Feature-Request Gate is a primary signal — record the evidence and escalate to the user for confirmation before continuing:
ticket comment <BUG_TICKET_ID> "Feature-Request Gate: feature-request language detected — <evidence from signal>"
Present the evidence to the user with three options:
- Close as feature request — close the bug ticket with
--reason="Fixed: Intent-contradicting — feature request, not bug". Only close if the user explicitly authorizes closure.
- Convert to epic — invoke
/dso:brainstorm on the ticket to create a proper feature epic. The brainstorm skill handles convert-to-epic flow.
- Proceed with investigation — treat as a genuine bug and continue to Phase C Step 1.
Do NOT close the ticket autonomously — feature request closure requires explicit user authorization. Do NOT implement the feature as a bug fix (8204-97b0).
-
triggered: false — No feature-request language detected. Proceed directly to Phase C Step 1 (Investigation Sub-Agent Dispatch).
Graceful degradation: If feature-request-check.py exits nonzero, produces empty stdout, or yields unparseable JSON, treat the result as triggered: false and proceed to Phase C Step 1 without blocking. Construct the fallback signal explicitly:
FEATURE_REQUEST_GATE_FALLBACK='{"gate_id":"feature_request","signal_type":"primary","triggered":false,"evidence":"Feature-Request Gate script failure — defaulting to non-blocking","confidence":"low"}'
Feature-Request Gate failure must never block a legitimate bug investigation.
Phase C: Investigation
Step 0: Parallel-Pipeline Integration (fix-bug:investigation scratch key)
/dso:debug-everything Bug-Fix Mode dispatches this skill via a two-phase parallel pipeline (skills/debug-everything/prompts/dispatch-fix-batch.md). Before Step 1, check the per-ticket scratch for a pre-computed investigation, and check the dispatch prompt for an investigation-only mode hint. Three paths follow:
Path A — Pre-loaded findings (FAST FORWARD). Read fix-bug:investigation from ticket scratch:
SCRATCH_INV=$(.claude/scripts/dso ticket scratch get "$BUG_TICKET_ID" fix-bug:investigation 2>/dev/null)
If the response is status:"hit", the value field is a JSON envelope conforming to fix-bug:investigation/v1. Inspect it:
- Normal projection —
summary + proposed_fix + affected_files + routing flags present, no scratch_overflow flag. Treat the projection as the output of Step 1 (the orchestrator confirmed the full RESULT lives in discovery_file if richer detail is needed). SKIP Step 1 dispatch and proceed to Step 2.
- Oversize fallback — envelope contains
"scratch_overflow": true and "discovery_file": "<path>". Read the full Investigation RESULT envelope from the discovery file:
DISCOVERY_FILE=$(echo "$SCRATCH_INV" | python3 -c '
import sys, json
envelope = json.loads(sys.stdin.read())
value = envelope.get("value")
if isinstance(value, str):
# ticket scratch wraps the stored JSON string; parse it.
value = json.loads(value)
print(value.get("discovery_file", "") if isinstance(value, dict) else "")
')
cat "$DISCOVERY_FILE"
Read sys.stdin exactly once — re-reading it returns the empty string and json.loads('') raises. Use the discovery-file contents as the Step 1 RESULT and proceed to Step 2.
If the response is status:"miss" or the projection is malformed (no bug_id match, no complexity, etc.), fall through to Path B/C — do NOT silently dispatch a fresh investigation, surface this as a pipeline contract violation in your sub-agent output before falling through.
Path B — MODE: investigation-only (TERMINATE AT END OF PHASE D). If the orchestrator dispatch prompt contains the literal token MODE: investigation-only, you are running the investigation half of the parallel pipeline. Two constraints apply:
- Investigate inline — do NOT dispatch investigator sub-agents. Sub-agents launched via the Agent tool cannot dispatch their own sub-agents (hard architectural constraint of Claude Code). Read source, grep callers, check git history, run hypothesis commands — execute the five-whys / hypothesis-generation / empirical-validation logic yourself using Read / Grep / Bash. The investigator agent definitions remain the canonical specification of the investigation rubric; in this mode you apply that rubric inline.
- Terminate at the end of Phase D Step 4 (scratch-write). Do NOT proceed to Phase E. The orchestrator will dispatch a separate sub-agent in the fix-application phase, which will take Path A above and execute Phases E–I from the scratch-loaded findings.
Path C — Normal mode. No scratch entry and no MODE: investigation-only token: proceed to Step 1 below as usual (dispatch investigator sub-agent per the normal contract).
Step 1: Investigation Sub-Agent Dispatch (/dso:fix-bug)
You MUST dispatch the investigation sub-agent described below unless Path A or Path B from Step 0 applies. Do NOT investigate inline — reading source code, grepping for patterns, running hypothesis commands, or analyzing the bug yourself does not satisfy this step. The sub-agent follows a rigorous investigation template (five whys, hypothesis generation, empirical validation) that prevents confirmation bias. Dispatch the sub-agent, await its RESULT report, then proceed to Phase C Step 2. Do not skip this step even if you have already conducted your own investigation.
Worktree isolation (applies to all sub-agent dispatches in this skill): Read and apply skills/shared/prompts/worktree-dispatch.md. Worktree isolation is always enabled — add isolation: "worktree" to each Agent dispatch and inject SESSION_BRANCH / SESSION_HEAD into each sub-agent prompt so the sub-agent can sync to session HEAD. Do NOT inject the orchestrator's session-worktree absolute path (per bug 9679-695c-6e11-4d95). After the sub-agent returns, compute ORCHESTRATOR_ROOT=$(git rev-parse --show-toplevel) in your own bash context and follow skills/shared/prompts/single-agent-integrate.md (which self-bootstraps ORCHESTRATOR_ROOT and uses it for the WORKTREE_PATH != ORCHESTRATOR_ROOT guard, harvest, and cleanup).
Dispatch investigation sub-agents based on the tier determined in Phase A Step 3. All sub-agents receive pre-loaded context before dispatch:
- Existing failing tests and their output
- Stack traces and error messages
- Relevant commit history (
git log --oneline -20 -- <affected-files>)
- Prior fix attempts from the ticket (if any)
Sub-agents must run existing tests immediately to establish a concrete failure baseline before analyzing code.
Structural Dependency Discovery (pre-loading — before sub-agent dispatch)
Before dispatching the investigation sub-agent, use structural search to pre-load callers, importers, and source-chain dependencies of the affected file(s). This discovers the bug's blast radius and gives the investigation sub-agent concrete scope rather than requiring it to re-discover callsites from scratch.
Use sg (ast-grep) for syntax-aware structural matching when available — it distinguishes real code references from comments and string literals. Fall back to Grep when sg is not installed:
if command -v sg >/dev/null 2>&1; then
sg --pattern 'import $MODULE' --lang python <repo_root>
sg --pattern 'from $MODULE import $_' --lang python <repo_root>
sg --pattern 'source $PATH' --lang bash <repo_root>
else
grep -r 'import <module_name>' <repo_root>
grep -r 'from <module_name>' <repo_root>
fi
Include the discovered callers, importers, and source-chain dependencies as additional context in the investigation sub-agent prompt. This structural pre-loading step complements the blast-radius.sh blast-radius analysis — both use the same command -v guard pattern. Note: blast-radius.sh checks command -v ast-grep (the package name) while new integrations use command -v sg (the CLI binary name); see CLAUDE.md "Structural Code Search (ast-grep)" for the canonical naming convention. Blast-Radius Gate (modifier gate: appends blast-radius annotation to escalation context; skips annotation silently on error) runs post-investigation; this step runs pre-dispatch.
Dispatch Context (all tiers)
Every investigator agent receives the same base context slots; the orchestrator populates them once and passes them with each dispatch. Tier-specific instructions (techniques, depth, RESULT extensions) are owned by the agent's delta — do not restate them here.
| Slot | Source |
|---|
{ticket_id} | The bug ticket ID (e.g., w21-xxxx) |
{failing_tests} | Output of $TEST_CMD — failing test names and their output |
{stack_trace} | Stack trace extracted from test output or error logs |
{commit_history} | Output of git log --oneline -20 -- <affected-files> |
{prior_fix_attempts} | Ticket notes containing previous fix attempt records (empty string if none) |
{escalation_history} (ESCALATED only) | Previous ADVANCED RESULT, discovery file contents, and — for the Empirical Agent — the RESULT reports from the three theoretical lenses |
All agents return RESULT conforming to the Investigation RESULT Report Schema defined below. INTERMEDIATE-and-above agents additionally return a root_cause_candidates array — at least 2 surviving causes, each with confidence (high/medium/low) and evidence (empirical observation, code reference, commit, or hypothesis_test verdict), ordered by descending confidence. The top candidate's cause matches the top-level ROOT_CAUSE, so downstream gates (hypothesis validation, fix approval, convergence scoring) continue to read ROOT_CAUSE unchanged. Surfacing surviving alternatives gives the orchestrator visibility into uncertainty when picking a fix and feeds the convergence/fishbone steps at ADVANCED.
BASIC Investigation (score < 3)
Dispatch dso:investigator-basic (sonnet).
INTERMEDIATE Investigation (score 3–5)
Dispatch one opus sub-agent based on availability:
- Primary (when
error-debugging:error-detective is available via discover-agents.sh): dso:investigator-intermediate
- Fallback (general-purpose dispatch path):
dso:investigator-intermediate-fallback
Investigation depth is identical between the two; only the persona framing differs.
ADVANCED Investigation (score ≥ 6)
Dispatch two opus sub-agents concurrently with differentiated lenses (launch both Task calls in one message with run_in_background: true, then await both):
dso:investigator-advanced-code-tracer — execution-path / code-evidence lens
dso:investigator-advanced-historical — timeline / change-history lens
Convergence Scoring (orchestrator step — after both agents return)
Compare the two ROOT_CAUSE fields:
- Full agreement (same or semantically equivalent):
convergence_score = 2 — proceed to fix selection with high confidence.
- Partial agreement (same subsystem, different specific defect):
convergence_score = 1 — present both in fix approval.
- Divergence (no category overlap):
convergence_score = 0 — proceed to fishbone synthesis.
Fishbone Synthesis (when convergence_score = 0)
Synthesize findings using the six fishbone categories — Code Logic, State, Configuration, Dependencies, Environment, Data. For each: merge Agent A and Agent B findings, note agreements/disagreements, weight by evidence strength. The synthesized fishbone becomes the unified root cause report for fix approval.
ESCALATED Investigation
Triggered when ADVANCED fails to produce a high-confidence root cause. Dispatch four opus sub-agents — three theoretical lenses concurrently first, then the empirical lens with their findings:
dso:investigator-escalated-web — Web Researcher (WebSearch/WebFetch authorized)
dso:investigator-escalated-history — History Analyst
dso:investigator-escalated-code-tracer — deep Code Tracer
dso:investigator-escalated-empirical — Empirical Agent (dispatched after the above three)
Dispatch sequencing: Launch the first three concurrently in one message with run_in_background: true. Once all three return, dispatch the Empirical Agent with the three theoretical RESULT reports populating {escalation_history}.
Veto Logic (after all four agents return)
After all four agents return their RESULT reports, evaluate Agent 4's veto_issued field:
- No veto (
veto_issued: false): proceed to fix selection with confidence weighted by Agent 4's empirical validation of the agents 1-3 consensus.
- Veto issued (
veto_issued: true): Agent 4's empirical evidence directly contradicts the root cause proposed by the consensus of agents 1-3. The veto supersedes the theoretical analysis. When a veto is issued, dispatch a resolution agent.
Resolution agent dispatch (on veto): The resolution agent receives all four RESULT reports, weighs the theoretical evidence from agents 1-3 against the empirical evidence from Agent 4, conducts additional targeted tests to break any remaining tie, and surfaces the highest-confidence conclusion. The resolution agent's conclusion governs fix selection.
Terminal Escalation
If ESCALATED investigation (with or without resolution agent) cannot produce a high-confidence root cause, this is the ESCALATED terminal condition. Log ESCALATED terminal — user escalation required and do NOT attempt any further autonomous fix. Surface all findings to the user:
- All root causes considered with confidence levels
- All fixes attempted with results
- All hypothesis test results
- All RESULT reports from agents 1-4 (and the resolution agent if dispatched)
- Recommendation for manual investigation
Step 2: Hypothesis Testing (/dso:fix-bug)
For each root cause proposed by Phase C Step 1:
- Propose a concrete test (bash command, unit test, or assertion) that would prove or disprove the suspected root cause
- Run the test
- Record the result in the discovery file (see Discovery File Protocol below)
Example:
echo '{"a.b": 1}' | python3 -c "import json,sys; d=json.load(sys.stdin); print('a.b' in d)"
Tests that confirm a root cause increase confidence. Tests that disprove a root cause eliminate it from consideration.
Scaffolding test support: Hypothesis validation tests written during Phase C Step 2 may be kept temporarily as scaffolding to support the fix implementation. If retained:
- Mark the test with a
## Scaffolding Test — remove after fix validation comment at the top of the test function/block so it is clearly identified as temporary.
- Do NOT register scaffolding tests in
.test-index with RED markers — they are temporary investigation artifacts, not part of the permanent TDD contract.
- Scaffolding tests must be removed during Phase E Step 4 (fix verification). See Phase E Step 4 for the explicit removal instruction.
Step 3: Hypothesis Validation Gate (/dso:fix-bug)
Before proceeding to fix approval or fix implementation, validate the hypothesis_tests section of the investigation RESULT report.
Gate logic (applied after Phase C Step 2 completes):
-
Check for hypothesis_tests entries: If the investigation RESULT has no hypothesis_tests section, or the section is missing or empty (zero entries), escalate to the next investigation tier. A missing or empty hypothesis_tests section means the investigation produced no testable root cause — fix implementation must not proceed without confirmed evidence.
-
Check for at least one confirmed verdict: If all hypothesis_tests entries have verdict: disproved or verdict: inconclusive (no verdict: confirmed entry exists), escalate to the next investigation tier. All hypotheses being disproved means the true root cause has not been identified — proceeding to fix implementation would be speculative.
-
Proceed only with confirmed evidence: If at least one hypothesis_tests entry has verdict: confirmed, check the observed field for evidentiary quality and the test field for evidence type. Apply the evidence taxonomy from skills/shared/prompts/empirical-validation.md:
- If the hypothesis is static (about artifact content — file exists, string present, config value is X):
grep, cat, Read, and similar static tools are valid. verdict: confirmed is acceptable.
- If the hypothesis is dynamic (about runtime behavior — workflow fires, cleanup runs, script triggers, code executes a path): static tools (
grep, cat, Read, head, wc) are NOT valid evidence. A test field that contains only grep/cat/Read commands for a dynamic hypothesis MUST be treated as verdict: inconclusive regardless of what was found. Escalate — the hypothesis was not empirically tested; source text was observed, not behavior.
- Detection heuristic: when the
hypothesis field contains runtime keywords ("runs", "fires", "triggers", "executes", "cleans", "sweeps", "handles", "emits", "skips") AND the test field contains only grep, cat, Read, head, or wc commands, classify the test as static-only and downgrade the verdict to inconclusive.
The observed field MUST contain empirical evidence — command output, test results, or concrete observations from running code. It must NOT contain only reasoning, inference, or "based on code reading" explanations. If the observed field contains only reasoning or static file content without dynamic execution evidence, treat the verdict as inconclusive and escalate. Proceed to check 4 below only when the observed field contains genuine empirical evidence from executing the code path.
-
Check hypothesis-root-cause consistency: The confirmed hypothesis must explain the ROOT_CAUSE identified in the investigation RESULT. If the confirmed hypothesis tests a different aspect than the ROOT_CAUSE (e.g., hypothesis confirms a config value exists but ROOT_CAUSE claims a code logic error), the gate fails — the investigation has confirmed a tangential fact, not the root cause. Escalate to the next investigation tier.
-
Causal chain termination check (bug 6b67-2aad): The ROOT_CAUSE must name a specific actionable artifact — a line of code, a prompt instruction, a config value, a specific variable or path that is wrong. Category labels ("platform issue," "LLM behavior," "non-deterministic compliance") are not root causes; they are descriptions of the symptom class. If ROOT_CAUSE terminates at a category label rather than an artifact, the investigation is incomplete — escalate to the next tier with the instruction: "Trace the causal chain one more step. What specific code, instruction, or configuration provides the wrong information to the actor?" A root cause that cannot be traced to a changeable artifact cannot produce a fix.
Only when checks 4 and 5 pass — the confirmed hypothesis directly explains the ROOT_CAUSE, and the ROOT_CAUSE names an actionable artifact — proceed to Phase D Step 1 (Fix Approval).
Escalation on gate failure: When the gate rejects the investigation result (missing/empty hypothesis_tests, or all disproved), escalate following the standard escalation path (BASIC → INTERMEDIATE → ADVANCED → ESCALATED → User). Include the gate failure reason and all investigation findings in the escalation context so the next tier can build on prior work.
GATE_FAILURE_REASON: no_confirmed_hypothesis
current_tier: <BASIC|INTERMEDIATE|ADVANCED|ESCALATED>
hypothesis_tests_count: <number of entries, 0 if missing>
confirmed_count: 0
finding_summary: <brief summary of what the investigation found before gate rejection>
Record the gate failure in the discovery file and as a ticket comment before escalating.
Phase D: Approval & Planning
Step 1: Fix Approval (/dso:fix-bug)
Determine whether the fix can be auto-approved or requires user input:
- Auto-approve if: there is exactly one proposed fix, AND the fix is high confidence + low risk + does not degrade functionality, AND the fix does not modify safeguard files in any direction (expansion, reduction, or refactoring of enforcement scope all require user approval), AND the fix does not introduce capabilities, configuration options, or environment variables that were absent from the system before the bug was reported
- User approval required if: the fix modifies safeguard files in any way — ANY modification including expansions that add new enforcement, reductions that remove enforcement, and refactors that change when enforcement fires are all disqualified from auto-approve. Note: adding new enforcement rules, guard functions, blocked-command patterns, or early-exit conditions to safeguard files counts as a safeguard file modification requiring user approval even when the intent is to strengthen security. OR the fix introduces new capabilities not described in the original bug report — i.e., adds code paths, CLI flags, environment variables, or configuration keys that did not exist before (feature creep — escalate to user; do NOT invoke
/dso:brainstorm from sub-agent context), OR multiple competing fixes with comparable confidence/risk, OR all fixes degrade functionality, OR confidence is medium or below
When presenting fixes for user approval, display:
- Each proposed fix with description, risk level, and whether it degrades functionality
- Confidence level in each root cause
- Confidence level in each fix
- Results from hypothesis testing (Phase C Step 2) alongside corresponding root causes
- Convergence notes (when multiple agents independently identified the same root cause or fix)
Step 2: Fix Complexity Evaluation (/dso:fix-bug)
Before writing a RED test or implementing the fix, evaluate the complexity of the proposed fix scope using the complexity-evaluator agent definition:
Read: ${CLAUDE_PLUGIN_ROOT}/agents/complexity-evaluator.md
Input: approved fix description, files affected, estimated change scope
Note: fix-bug reads the complexity-evaluator agent definition inline (rather than dispatching a sub-agent) to avoid nested dispatch — fix-bug often runs as a sub-agent of debug-everything, and dispatching a sub-agent from within a sub-agent risks rule:no-nested-task failures. The agent definition file contains the same five-dimension rubric and classification rules.
TRIVIAL or MODERATE fix: proceed to Phase E Step 1 (RED Test).
COMPLEX classification triggers (any one is sufficient):
- Fix requires changes to 5+ files across 3+ distinct subsystems (directories)
- Fix requires modifying both a skill/agent definition AND its corresponding script/hook
- Investigation identified 3+ interacting root causes that cannot be addressed independently
- Prior fix attempt failed and the failure analysis indicates the fix scope was too narrow
When ANY trigger fires, classify as COMPLEX — do not rationalize ("these are small changes", "they're all related") to avoid escalation. The triggers exist because past sessions showed agents resolving complex bugs with incomplete fixes that caused regressions.
COMPLEX fix: the fix scope is too large for a single bug fix track. The behavior depends on execution context:
When running as orchestrator (not a sub-agent):
- Record the finding:
ticket comment <BUG_TICKET_ID> "Fix complexity: COMPLEX — escalating to epic"
- Invoke
/dso:brainstorm to create an epic for the refactor or larger change
- Stop — do NOT proceed to Phase E Step 1 or Phase E Step 3 in this session
When running as a sub-agent (detected per Sub-Agent Context Detection below):
- Record the finding:
ticket comment <BUG_TICKET_ID> "Fix complexity: COMPLEX — returning escalation to orchestrator"
- Return a COMPLEX_ESCALATION report to the calling orchestrator instead of invoking
/dso:brainstorm directly (sub-agents cannot reliably invoke skills):
COMPLEX_ESCALATION: true
escalation_type: COMPLEX
bug_id: <ticket-id>
investigation_tier_needed: orchestrator-level re-dispatch
investigation_findings: <summary of root cause candidates, confidence, and evidence from investigation>
escalation_reason: <why the fix is COMPLEX — e.g., cross-system refactor, multiple subsystems affected>
Note for the re-dispatched agent (not actionable in the current dispatch): when the fix track resumes, the implementation agent must consult skills/shared/prompts/prior-art-search.md before writing any fix code — see the prior-art instruction in Fix Implementation.
- Stop — do NOT proceed to Phase E Step 1 or Phase E Step 3. The orchestrator receives this report and decides how to proceed (e.g., re-dispatch
/dso:fix-bug at orchestrator level with full authority, or invoke /dso:brainstorm to create an epic).
Step 3: Testing Mode Classification (/dso:fix-bug)
Before dispatching a RED test or modifying existing tests, classify the fix into one of three testing modes. This determines which Phase E Step 1 path to follow.
Path-B termination check (parallel-pipeline integration)
If Phase C Step 0 selected Path B (MODE: investigation-only), proceed to Phase D Step 4 below and terminate after the scratch-write. Do not classify testing mode in investigation-only mode — that decision belongs to the fix-application sub-agent which the orchestrator will dispatch next.
Examine the investigation RESULT root cause and the approved fix description from Phase D Step 1.
Classification rules:
| testing_mode | Condition |
|---|
GREEN | The fix changes implementation without changing observable behavior (e.g., performance optimization, internal restructuring, refactor). Existing tests remain valid and do not need modification. |
UPDATE | The fix changes observable behavior AND existing tests already cover the affected paths — but those tests currently assert the old (buggy) behavior. The existing tests need to be updated to assert the new correct behavior. |
RED | The fix changes observable behavior AND no existing tests cover the affected paths. A new failing test must be written before the fix is applied. |
Default: GREEN — most bug fixes change implementation rather than observable behavior. Apply this default when the fix is an internal correction that does not alter public-facing outputs, return values, exit codes, or emitted events.
Visual/UI/CSS changes are behavioral — do NOT default to GREEN. Any fix that changes rendered output, CSS classes applied, layout, component appearance, or UI structure changes observable behavior (what the user sees). Classify as RED or UPDATE (per existing test coverage), NOT GREEN. The reasoning "unit tests don't assert on CSS classes" is invalid here — the classification is based on whether observable behavior changes, not on whether the current test suite happens to cover it. If no test covers the visual change, that is precisely the gap RED mode exists to fill. Do NOT make a test-layer inference at classification time; default to RED for visual changes and let the red-test-writer determine the appropriate assertion strategy.
Emit the classification signal on its own line:
testing_mode=<GREEN|UPDATE|RED>
Proceed to the corresponding Phase E Step 1 branch below.
Step 4: Investigation-Only Scratch Write & Terminate (MODE: investigation-only only)
This step runs only when Phase C Step 0 selected Path B. In every other case, skip directly to Phase E.
Size constraint. ticket scratch set rejects payloads exceeding 4096 bytes ({status:"error",code:"oversize"} — see ticket-cli-reference.md). The full Investigation RESULT envelope at INTERMEDIATE/ADVANCED tier easily exceeds this ceiling (root_cause_candidates × ≥2, alternative_fixes × ≥2, evidence arrays, fishbone categories). The scratch entry therefore stores a compact projection sufficient for the Phase 2 fix-application sub-agent; the full RESULT envelope is written to a side-car discovery file whose path is referenced in the scratch projection.
Step 4a — write the full Investigation RESULT envelope to a discovery file (no size ceiling):
DISCOVERY_FILE=$(mktemp /tmp/fix-bug-discovery-XXXXXX.json)
if [ -z "$DISCOVERY_FILE" ] || [ ! -w "$DISCOVERY_FILE" ]; then
DISCOVERY_FILE="/tmp/fix-bug-discovery-${BUG_TICKET_ID}.json"
printf 'WARNING: mktemp failed; falling back to deterministic discovery file path %s\n' "$DISCOVERY_FILE" >&2
fi
cat > "$DISCOVERY_FILE" <<EOF
<Investigation RESULT envelope from inline investigation — root_cause_candidates, alternative_fixes, hypothesis_tests, fishbone_categories, tradeoffs_considered, recommendation>
EOF
Step 4b — assemble the compact projection and write it to ticket scratch under the fix-bug:investigation key. This is the binding handoff to Phase 2. Keep it under 4 KB:
INVESTIGATION_JSON=$(cat <<EOF
{
"schema": "fix-bug:investigation/v1",
"bug_id": "${BUG_TICKET_ID}",
"summary": "<1-2 sentence root cause summary>",
"proposed_fix": "<one-paragraph fix description (recommendation only — not the full alternative_fixes list)>",
"affected_files": ["<path>", "..."],
"complexity": "<TRIVIAL|MODERATE|COMPLEX>",
"fixable": <true|false>,
"manual_approval_needed": <true|false>,
"complex_escalation": <true|false>,
"discovery_file": "${DISCOVERY_FILE}"
}
EOF
)
SCRATCH_RC_OUT=$(.claude/scripts/dso ticket scratch set "$BUG_TICKET_ID" fix-bug:investigation "$INVESTIGATION_JSON" 2>&1)
SCRATCH_RC=$?
if [ "$SCRATCH_RC" -ne 0 ] || echo "$SCRATCH_RC_OUT" | grep -q '"code":"oversize"'; then
FALLBACK_JSON=$(cat <<EOF
{
"schema": "fix-bug:investigation/v1",
"bug_id": "${BUG_TICKET_ID}",
"scratch_overflow": true,
"discovery_file": "${DISCOVERY_FILE}",
"complexity": "<TRIVIAL|MODERATE|COMPLEX>",
"fixable": <true|false>,
"manual_approval_needed": <true|false>,
"complex_escalation": <true|false>
}
EOF
)
.claude/scripts/dso ticket scratch set "$BUG_TICKET_ID" fix-bug:investigation "$FALLBACK_JSON" || true
fi
Step 4c — emit the compact summary on the FINAL line of your sub-agent output. The orchestrator parses this verbatim and uses it for Phase 2 routing decisions. All six routing tokens are required so the orchestrator does not need to re-open the scratch entry to decide routing:
INVESTIGATION_COMPLETE: <bug-id>
COMPLEXITY: <TRIVIAL|MODERATE|COMPLEX>
FIXABLE: <true|false>
MANUAL_APPROVAL_NEEDED: <true|false>
COMPLEX_ESCALATION: <true|false>
SCRATCH_KEY: fix-bug:investigation
TERMINATE NOW. Do not proceed to Phase E. The orchestrator's fix-application phase will dispatch a new sub-agent that loads this scratch entry (or the discovery file if scratch_overflow=true) and runs Phases E–I.
Phase E: TDD Fix Loop
Step 1: RED Test (/dso:fix-bug)
Standard reference: Load skills/shared/prompts/behavioral-testing-standard.md before writing or modifying any test. Apply all five rules (coverage check, observable behavior, execute-don't-inspect, refactoring litmus test, instruction-file structural boundary) to every test written or modified in this step.
When the bug fix changes only skill/CLAUDE.md/agent instruction files (LLM-behavioral), do NOT write tests that grep skill files for specific strings. Per behavioral testing standard rule 5, instruction files must be tested by their structural boundary (heading structure, required sections), NOT by content strings. Tests like `grep -q "Step 2.5: Worktree Ancestry Gate"` or `grep -qE "if.*code_version.*empty"` are change-detector tests — they break on every normal refactoring of the skill without testing any observable behavior. The only valid structural tests for skill files check: (a) required section headings exist, or (b) required commands/script paths are callable. Content strings are NOT structural boundaries. If a code reviewer flags "no test covers the new gate behavior" for an instruction-file-only change, the correct response is R5 defense (instruction files are tested by structure, per standard rule 5), not writing a content-grepping test.
If the bug already causes an existing test to fail, skip this step — the existing test serves as the RED test.
Branch based on testing_mode from Phase D Step 3:
testing_mode=GREEN — Skip RED test
The fix changes implementation without changing observable behavior. Existing tests validate fix correctness.
Log: Testing mode: GREEN — existing tests validate fix correctness. Proceeding to implementation.
Proceed directly to Phase E Step 3 (Fix Implementation). No RED test is written and the pre-fix gate (Phase E Step 2) is skipped — existing tests validate correctness after the fix is applied.
testing_mode=UPDATE — Modify existing tests
The fix changes observable behavior and existing tests cover the affected paths, but they assert old behavior.
- Identify the existing test(s) that cover the affected behavior.
- Update those tests to assert the new expected (correct) behavior BEFORE implementing the fix. The updated tests should now fail (they assert the new behavior, but the code still has the bug).
- Run the updated tests to confirm they fail (RED state).
- Proceed to Phase E Step 2 using the updated test(s) as the confirmation of the RED state — the gate will verify they are failing before fix dispatch.
testing_mode=RED — Dispatch red-test-writer
The fix changes observable behavior and no existing tests cover the affected paths. Write a new failing test before implementing the fix.
RED Test Dispatch via dso:red-test-writer
Dispatch a task to dso:red-test-writer (sonnet) with the bug context (bug description, root cause from investigation, files affected, and the approved fix description from Phase D Step 1).
Parse the leading TEST_RESULT: line from the output:
| Result | Action |
|---|
TEST_RESULT:written | Success. Proceed to Phase E Step 2 using TEST_FILE and RED_ASSERTION fields. |
TEST_RESULT:rejected | This inline dispatch was the sonnet attempt. On rejection, proceed to Tier 2 of the escalation protocol in skills/sprint/prompts/red-task-escalation.md (skip Tier 1 — already attempted here). TEST_RESULT:rejected is not an infrastructure failure. See fix-bug verdict mapping below. |
| Timeout / malformed / non-zero exit | Treat as TEST_RESULT:rejected. Proceed to Tier 2 of the escalation protocol. |
Fix-bug verdict mapping (how escalation verdicts map to fix-bug workflow):
VERDICT:CONFIRM (TDD infeasible) → return to Phase C Step 1 and escalate to the next investigation tier. The bug may require a different fix approach that is testable.
VERDICT:REVISE (task spec insufficient) → re-run investigation (Phase C Step 1) with the evaluator's revision guidance appended to the investigation context.
VERDICT:REJECT (retry at opus) → proceed to Tier 3 per the escalation template.
When TEST_RESULT:written, run the new test to confirm it fails (RED):
$TEST_CMD
The test failure should confirm the root cause identified during investigation when possible.
If a previous investigation loop created a RED test for this bug, the existing test may be edited rather than creating a new one — dispatch dso:red-test-writer with the existing test file path so it can update rather than create.
If no RED test can be written (all three tiers in red-task-escalation.md are exhausted): return to Phase C Step 1 and escalate to the next investigation tier. Include the rejection payloads and reasoning with the investigation prompt.
Step 2: RED-before-fix Gate (/dso:fix-bug)
Mechanical bug exemption: This gate does NOT apply to mechanical bugs (import errors, lint violations, config syntax errors, type annotations) routed through the Mechanical Fix Path. Those bugs bypass Steps 2–5 entirely and proceed directly from Phase A Step 3 to a direct fix. The Mechanical Fix Path has no RED test requirement because the fix is deterministic and verified by running $TEST_CMD and $LINT_CMD after applying it.
Before dispatching any fix implementation (Phase E Step 3), verify that a RED test exists and has been confirmed failing. This gate blocks any code modification — Edit, Write, or fix sub-agent dispatch — until it is satisfied.
Gate logic (applied after Phase E Step 1 completes):
-
Check that a RED test exists: If Phase E Step 1 was skipped because an existing test was already failing, that test counts as the RED test. If Phase E Step 1 was executed, the new test written there is the RED test.
-
Check that the RED test has been confirmed failing: The RED test must have been run and confirmed to fail before fix implementation proceeds. If the test was not run or the run result is not available, run it now:
$TEST_CMD
If the test does not fail, do NOT proceed to Phase E Step 3. Return to Phase E Step 1 to diagnose why the test passes unexpectedly — this indicates either the test is wrong or the bug is already fixed.
-
Do not proceed to Phase E Step 3 if the RED test has not been confirmed failing. Any code modification (Edit, Write, sub-agent fix dispatch) is blocked until the RED test is confirmed failing in a test run output you have observed in this session.
Gate failure action: If no RED test can be confirmed failing, do NOT skip to fix implementation. Return to Phase E Step 1 and address why the RED test cannot be confirmed.
LLM-behavioral bug exemption: This gate is relaxed for llm-behavioral bugs. LLM behavioral bugs (prompt regressions, agent guidance gaps, skill misinterpretation) cannot always have a traditional executable RED unit test written before the fix — the behavioral regression lives in natural language instructions, not in executable code paths. For llm-behavioral bugs, the RED unit test requirement is replaced with eval-based verification: define an eval assertion that would fail with the current skill/agent/prompt content and pass after the fix. If no eval framework is available, document the behavioral assertion in the ticket as the verification criterion before proceeding to fix implementation.
Step 2.5: Hermetic Reproduction Gate (/dso:fix-bug)
After the RED test is confirmed failing (Step 2), run the hermetic reproduction helper to verify the failure reproduces in an isolated environment. This gate applies only to shell/bash RED tests (.sh suffix or $TEST_CMD runner starts with bash or sh).
"${CLAUDE_PLUGIN_ROOT}/scripts/run-hermetic.sh" "$TEST_CMD"
Interpret the helper output:
| Outcome | Output | Action |
|---|
| Shell reproduction confirmed | exits 0 | Proceed to Step 3 (Fix Implementation). |
| Shell non-reproduction | exits non-zero | HARD-GATE: halt implementation immediately (see below). |
| Non-shell test | emits HERMETIC_SKIP: runner=<runner> reason=non-shell | Record the HERMETIC_SKIP line in session notes and proceed to Step 3 — non-shell tests are never blocked by this gate. |
**Shell non-reproduction — full stop.**
If run-hermetic.sh exits non-zero for a shell/bash RED test, the bug does NOT reliably reproduce in a hermetic environment. Implementation is blocked.
- Post a ticket comment:
Hermetic reproduction failed (include the helper output as context).
- Do NOT dispatch a fix sub-agent or make any code changes.
- Return to Phase C (Investigation) to re-examine the environment assumptions underlying the root cause. The hermetic failure indicates the root cause analysis may depend on local state, path, or configuration that is not present in a clean environment.
Non-shell path (never blocked): When run-hermetic.sh emits HERMETIC_SKIP: runner=<runner> reason=non-shell, the gate is automatically satisfied — record the skip line and proceed directly to Fix Implementation. Non-shell tests (pytest, node, etc.) are outside the hermetic shell-isolation contract and are never blocked by this gate.
Step 3: Fix Implementation (/dso:fix-bug)
Exploration Decomposition: During investigation, when a diagnostic question is compound or spans multiple sources (multiple codebase layers, web research, or ambiguous scope), apply the shared exploration decomposition protocol at skills/shared/prompts/exploration-decomposition.md. Classify as SINGLE_SOURCE or MULTI_SOURCE. Emit DECOMPOSE_RECOMMENDED when a factor is unspecified or two findings directly contradict.
Prior-Art Search: Before dispatching the fix sub-agent, consult the shared prior-art search framework at skills/shared/prompts/prior-art-search.md. This ensures the fix approach does not duplicate an existing pattern, introduce an inconsistent abstraction, or miss a reuse opportunity. Apply the Routine Exclusions section of that framework — single-file logic fixes that correct a clear bug without introducing new abstractions are exempt. For fixes that add new helpers, patterns, or abstractions, run at least Tier 1–2 of the tiered search protocol before dispatching the fix sub-agent.
HARD-GATE: Before dispatching the fix sub-agent, the orchestrator MUST have a root_cause_report produced by the investigation sub-agent Task tool call (Phase C Step 2 / LLM-Behavioral Fix Path). The orchestrating agent may not produce the root_cause_report itself — it must come from the prior investigation sub-agent's RESULT output. If no root_cause_report is present, do NOT proceed to fix dispatch; return to the appropriate investigation step.
Exemptions:
- mechanical bugs exempt: Mechanical fix path (syntax errors, import errors, lint violations, config syntax) bypasses the investigation sub-agent entirely. The orchestrator proceeds directly to fix dispatch without requiring a
root_cause_report from a sub-agent Task call.
- bot-psychologist path exempt: When the llm-behavioral classification routes through the
dso:bot-psychologist agent (LLM-Behavioral Fix Path), the bot-psychologist produces its own structured output. The root_cause_report requirement from the standard investigation path does not apply; the bot-psychologist's RESULT serves as the equivalent structured input for the fix sub-agent.
Classification boundary (behavioral vs. mechanical):
- behavioral: prompt regressions, agent guidance gaps, skill misinterpretation, incorrect model decisions, LLM output drift — requires investigation sub-agent or bot-psychologist
- mechanical: import errors, syntax errors, lint violations, config parse errors, missing files — deterministic root cause, no investigation sub-agent required
Launch a sub-agent to implement the approved fix per the worktree-isolation block in this skill's investigation step:
- The sub-agent receives the full investigation RESULT (root cause, confidence, approved fix) as
root_cause_report
- Change ONLY what is necessary — no refactoring, no scope creep
- One logical change at a time
- Model override for LLM-behavioral fixes: When the fix requires editing files in
skills/, agents/, or prompts/ directories (LLM prompt creation or editing), dispatch the fix sub-agent with model: "opus". These changes alter agent reasoning and require deeper judgment than code changes.
Multi-commit RED→GREEN discipline (applies when the fix spans ≥2 atomic commits across multiple axes — e.g., multiple subsystems, sync directions, or independent behavior paths): Each atomic commit MUST independently demonstrate RED→GREEN. The fix sub-agent MUST follow this sequence for every behavioral commit:
- Write the RED test for this commit's behavior FIRST
- Run the test — it MUST fail (RED); capture the failure output
- Implement the fix for this commit
- Re-run the test — it MUST pass (GREEN)
- Commit only when GREEN is confirmed; proceed to the next axis