debug
SkillFiles & storageInvestigation-first debugging — gather evidence, form confirmed root-cause hypothesis, hand off to fix mode with diagnosis file. TRIGGER when: user reports a symptom or failing test with Python traceback, or asks to investigate a runtime/CI failure with reproducible evidence; phrases: "debug this failure", "why is X broken", "find the root cause of <error>", "investigate this CI failure". SKIP when: pure config quality issues (use `/foundry:audit`); broad system-wide diagnosis without traceback (use `/foundry:investigate`); user already knows the fix (use `/develop:fix`); non-Python project.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the debug skill
What this skill tells your AI
The instructions your AI receives, as published by borda/ai-rig in plugins/cc_develop/skills/debug/SKILL.md and read by ahel’s review.
Investigation-first debugging. Gather evidence, trace data flow, form confirmed root-cause hypothesis, hand off to fix mode.
NOT for: production incidents without any CI run ID or local traceback (use /foundry:investigate (requires foundry plugin) for triage); .claude/ config issues (use /foundry:audit (requires foundry plugin)); non-Python projects (JS/TS/Go/Rust) — toolchain assumes pytest; use language-native toolchain instead. CI-only failures ARE supported — pass --ci-run <run-id or URL> to use GitHub Actions logs as evidence source.
Issue ID routing note: issue mode selected when --issue flag present, or when argument (after other flags stripped) is a pure run of digits with an optional # prefix (e.g. 123 or #123). No numeric threshold. Pass --issue <N> to force issue mode for any argument.
- Key boundary: after Steps 1+2 — evidence gathered and pattern analysis complete, before hypothesis gate (Step 3).
- Preserve: debug mode, CI run ID if set, evidence signals (issue body, test path), tried-hypotheses ledger (candidate causes + verdicts — refuted/ruled-out/open), --keep items.
- Refresh also after any Step 3 probe that rules out a hypothesis — so post-compact gate does not re-test refuted causes (loop guard).
Agent Resolution
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
_DEV_SHARED=$(python "${CLAUDE_PLUGIN_ROOT:-plugins/cc_develop}/bin/dev_shared_resolve.py" 2>/dev/null) # timeout: 5000
[ -z "$_DEV_SHARED" ] && _DEV_SHARED="plugins/cc_develop/skills/_shared"
echo "$_DEV_SHARED" > "${TMPDIR:-/tmp}/dev-shared-${CSID}" # cold resolve — every later block warm-reads this
# loads: compaction-contract.md
cat "$_DEV_SHARED/agent-resolution.md"
Contains: foundry check + fallback table. If foundry not installed: substitute each foundry:X with general-purpose per table. Agents this skill uses: foundry:sw-engineer, foundry:challenger.
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
IFS= read -r _DEV_SHARED < "${TMPDIR:-/tmp}/dev-shared-${CSID}" 2>/dev/null || _DEV_SHARED="" # timeout: 5000
[ -z "$_DEV_SHARED" ] && _DEV_SHARED="plugins/cc_develop/skills/_shared"
cat "$_DEV_SHARED/task-hygiene.md"
Project Detection
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
IFS= read -r _DEV_SHARED < "${TMPDIR:-/tmp}/dev-shared-${CSID}" 2>/dev/null || _DEV_SHARED="" # timeout: 5000
[ -z "$_DEV_SHARED" ] && _DEV_SHARED="plugins/cc_develop/skills/_shared"
cat "$_DEV_SHARED/runner-detection.md"
Sets $TEST_CMD (full suite) and $PYTEST_CMD (pytest flags). Run at skill start.
Language preflight gate: detect project language; adjust test runner accordingly.
# timeout: 5000
LANG_HINT="python"
if [ ! -f "pyproject.toml" ] && [ ! -f "setup.py" ] && [ ! -f "setup.cfg" ] && [ ! -f "Pipfile" ]; then
if [ -f "package.json" ]; then LANG_HINT="node"
elif [ -f "go.mod" ]; then LANG_HINT="go"
elif [ -f "Cargo.toml" ]; then LANG_HINT="rust"
fi
fi
If LANG_HINT not python: invoke AskUserQuestion — "Non-Python project detected ($LANG_HINT). Toolchain assumes pytest. How to proceed?" · (a) Abort — use language-native runner · (b) Continue — repo also has Python sources. On Abort: stop.
Checkpoint: debug = investigation only — no code changes. .plans/active/debug_<slug>.md (written in Step 4) serves as implicit session state. No .developments/ checkpoint needed.
Flag parsing
Parse flags into actual shell variables (not prose) so downstream blocks see correct values:
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
KEEP_ITEMS=""
if [[ "$ARGUMENTS" =~ --keep[[:space:]]\"([^\"]+)\" ]]; then
KEEP_ITEMS="${BASH_REMATCH[1]}"
fi
echo "$KEEP_ITEMS" > "${TMPDIR:-/tmp}/dev-debug-keep-items-${CSID}"
rm -f .temp/state/skill-contract.md ${TMPDIR:-/tmp}/dev-debug-hypotheses-${CSID} # timeout: 5000
# timeout: 10000
python "${CLAUDE_PLUGIN_ROOT:-plugins/cc_develop}/bin/dev_parse_args.py" \
--skill debug --write-files "$ARGUMENTS"
# URL normalization + log fetching: §URL Normalization in ci-log-extract.md
Worktree isolation
loads: worktree-isolation.md
When --worktree set, run the investigation in an isolated git worktree so reproduction attempts (repro scripts, temp edits) can never mutate the main sources — before codemap detection or Step 1.
# timeout: 5000
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
IFS= read -r WORKTREE_ENABLED < "${TMPDIR:-/tmp}/dev-debug-worktree-${CSID}" 2>/dev/null; [ "$WORKTREE_ENABLED" = "true" ] || WORKTREE_ENABLED=false
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
IFS= read -r _DEV_SHARED < "${TMPDIR:-/tmp}/dev-shared-${CSID}" 2>/dev/null || _DEV_SHARED="" # timeout: 5000
[ -z "$_DEV_SHARED" ] && _DEV_SHARED="plugins/cc_develop/skills/_shared"
cat "$_DEV_SHARED/worktree-isolation.md"
WORKTREE_ENABLED=true → follow §Enter (base off HEAD, EnterWorktree(path=…)). Read-only skill — obey §Deliverable: the diagnosis file is written to the main tree ($_ORIG_ROOT) at Step 4 so /develop:fix can read it. Else skip — run in main tree.
Codemap resolve — CODEMAP_RAW already written to ${TMPDIR:-/tmp}/dev-debug-codemap-${CSID} (per-skill) and ${TMPDIR:-/tmp}/dev-codemap-raw-${CSID} (legacy) by flag-parsing block above (via dev_parse_args.py --skill debug --write-files). Read per-skill path, then normalize via codemap_resolve.py:
# timeout: 5000
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
# skill-specific paths (dev-debug-codemap-*) avoid stale value from a prior feature --codemap run
CODEMAP_ENABLED=$(python "${CLAUDE_PLUGIN_ROOT:-plugins/cc_develop}/bin/dev_codemap_gate.py" debug) || exit 1
loads: codemap-gates.md
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
IFS= read -r _DEV_SHARED < "${TMPDIR:-/tmp}/dev-shared-${CSID}" 2>/dev/null || _DEV_SHARED="" # timeout: 5000
[ -z "$_DEV_SHARED" ] && _DEV_SHARED="plugins/cc_develop/skills/_shared"
cat "$_DEV_SHARED/codemap-gates.md"
Follow Gate A and Gate B.
Downstream blocks read back: IFS= read -r CHALLENGE_ENABLED < "${TMPDIR:-/tmp}/dev-challenge-enabled-${CSID}" 2>/dev/null || CHALLENGE_ENABLED=true, IFS= read -r CHALLENGE_FORCED < "${TMPDIR:-/tmp}/dev-challenge-forced-${CSID}" 2>/dev/null || CHALLENGE_FORCED=false, IFS= read -r TEAM_MODE < "${TMPDIR:-/tmp}/dev-team-mode-${CSID}" 2>/dev/null || TEAM_MODE=false, IFS= read -r CI_RUN_ID < "${TMPDIR:-/tmp}/dev-ci-run-id-${CSID}" 2>/dev/null || CI_RUN_ID="".
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
IFS= read -r _DEV_SHARED < "${TMPDIR:-/tmp}/dev-shared-${CSID}" 2>/dev/null || _DEV_SHARED="" # timeout: 5000
[ -z "$_DEV_SHARED" ] && _DEV_SHARED="plugins/cc_develop/skills/_shared"
cat "$_DEV_SHARED/ci-log-extract.md"
Follow §URL Normalization to set CI_RUN_ID. If CI_RUN_ID set, follow §Log Fetching and §Log Parsing to set CI_LOG_EVIDENCE; use it as evidence source in Step 1 instead of local pytest.
Unsupported flag check — after ALL supported flags extracted (including --issue and --keep from blocks above), scan $ARGUMENTS for remaining --<token> tokens not in supported list. Do NOT include --issue or --keep in "unknown" set — both are consumed by the mode-detect and keep-parse blocks above. Supported: --no-challenge, --challenge, --team, --worktree, --ci-run, --issue, --repo, --codemap, --no-codemap, --keep. If truly unknown token found: print ! Unknown flag(s): `--<token>`. then invoke AskUserQuestion — (a) Abort (stop, re-invoke with correct flags) · (b) Continue ignoring (skip unknown flags, proceed). On Abort: stop.
Mode selection — debug runs in one of two mutually-exclusive modes; set explicitly before any Step:
# timeout: 5000
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
# strip flags first — "123 --no-challenge" would fail integer detection otherwise
ARGUMENTS_FOR_MODE_DETECT=$(echo "$ARGUMENTS" | sed -E 's/--no-challenge|--challenge|--team|--worktree|--ci-run[= ]?[^ ]+|--issue|--repo[= ]?[^ ]+|--no-codemap|--codemap|--keep +"[^"]+"//g' | xargs)
if [[ " $ARGUMENTS " == *" --issue "* ]] || [[ "$ARGUMENTS_FOR_MODE_DETECT" =~ ^#?[0-9]+$ ]]; then
DEBUG_MODE="issue"
else
DEBUG_MODE="symptom"
fi
echo "$DEBUG_MODE" > ${TMPDIR:-/tmp}/dev-debug-mode-${CSID}
Subsequent steps branch by DEBUG_MODE:
- Issue mode: Step 1 fetches issue body and extracts test path before invoking pytest; skip symptom-text pytest block. Stop after Step 4 (handoff) — do not run symptom-text branches.
- Symptom mode: Step 1 skips issue fetch; uses free-text symptom directly. Skip issue-mode pytest block entirely.
If TEAM_MODE=true — execute team investigation now in place of standard Steps 1-2. After team synthesis completes, run Steps 3-4 inline (hypothesis gate + handoff to fix) on winning hypothesis — do not return to standard Steps 1-2. Authoritative reading: team mode replaces Steps 1-2 (parallel hypothesis investigation supplants serial evidence gathering); Steps 3-4 still execute (inline within this block, not by looping back to standard workflow):
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"; IFS= read -r _DEV_SHARED < "${TMPDIR:-/tmp}/dev-shared-${CSID}" 2>/dev/null || _DEV_SHARED=""; [ -z "$_DEV_SHARED" ] && _DEV_SHARED="plugins/cc_develop/skills/_shared"; cat "$_DEV_SHARED/preflight-helpers.md"§Team Spawn Template. Confirm[ROLE_PHRASE]= symptom text (from$ARGUMENTSstripped of flags),[FILE_SLUG]=debug-hypothesis.- Run project detection (
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"; IFS= read -r _DEV_SHARED < "${TMPDIR:-/tmp}/dev-shared-${CSID}" 2>/dev/null || _DEV_SHARED=""; [ -z "$_DEV_SHARED" ] && _DEV_SHARED="plugins/cc_develop/skills/_shared"; cat "$_DEV_SHARED/runner-detection.md") to set$TEST_CMDand$PYTEST_CMD. - Compute
TS=$(date -u +%Y-%m-%dT%H-%M-%SZ)andmkdir -p ".temp/develop/$TS". Spawn 2-3foundry:sw-engineeragents (model=opus) in parallel — each investigating one independent root-cause hypothesis. Use Team Spawn Template from preflight-helpers: replace[ROLE_PHRASE]with symptom,[FILE_SLUG]withdebug-hypothesis, assign each agent a distinct hypothesis number N. Each agent writes full output to.temp/develop/$TS/debug-hypothesis-N-$TS.md(run-dir timestamp, matching preflight-helpers §Team Spawn Template) and returns compact JSON{"status":"done","file":"<path>","findings":N,"confidence":0.N,"summary":"<one-line description of hypothesis>"}. - Coordination: lead broadcasts
{symptom: <description>, traceback: <key lines>}to teammates before spawning. After all return, facilitate cross-challenge between competing analyses. Convergence rule: select hypothesis with most direct evidence (observable in code or logs); if truly tied, invokeAskUserQuestionpresenting top 2 competing hypotheses. - Synthesis trace (lead, inline — no spawn): after individual teammate reports, lead reads all teammate findings from
.temp/develop/$TS/debug-hypothesis-*.md(2-3 files already on disk) and produces the unified cross-cutting trace map itself — entry point, modules crossed, state mutations, invariant violations across hypotheses. Write to.temp/develop/$TS/debug-trace-synthesis.md. A dedicated synthesis agent costs ~120,851 tok of fixed overhead for a read-and-merge the lead performs inline in fix's equivalent step — spawn nothing here. - Lead synthesises consensus root cause from the trace map + competing hypotheses. Run Steps 3-4 of standard workflow (hypothesis gate + hand off to fix) on winning hypothesis — execute those steps inline here; do not loop back through Steps 1-2. Step 3 gate in team mode: if convergence reached by synthesis agent (all hypotheses point to same root cause with high confidence), present converged hypothesis without a new user confirmation prompt — state "Team converged on root cause (no ambiguity)" and proceed directly to Step 4 handoff. Only invoke
AskUserQuestionat Step 3 if competing hypotheses remain or convergence declared by default (tied evidence).
Health monitoring (CLAUDE.md §6): for each spawned agent, use a per-agent sentinel keyed on loop counter $N (not literal N). Loop over agent indices in actual bash:
# timeout: 5000
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
for N in 1 2 3; do
touch "${TMPDIR:-/tmp}/debug-team-check-${N}-${CSID}"
done
Poll each independently every 5 min via find .temp/develop/$TS -name "debug-hypothesis-${N}*" -newer ${TMPDIR:-/tmp}/debug-team-check-${N}-${CSID} | wc -l where $N is actual agent index in loop variable — the -name scope is load-bearing: a directory-wide find marks every agent alive whenever any sibling writes, collapsing exactly the per-agent isolation these sentinels exist for. Poll only the indices actually spawned (2-hypothesis run → poll N=1,2 only; the third touched sentinel is a harmless unused file). A single shared sentinel collapses health isolation — stalled agent N=2 cannot be distinguished from active agent N=1. Hard cutoff 15 min no-file-activity per agent; mark timed-out agents with ⏱ in synthesis.
Step 1: Understand the symptom
Collect all signals before forming any hypothesis.
Structural context (codemap-py — only if CODEMAP_ENABLED=true): if index available, run before codebase exploration to pre-load blast-radius context for failing module:
# timeout: 10000
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
IFS= read -r CODEMAP_ENABLED < "${TMPDIR:-/tmp}/dev-debug-codemap-enabled-${CSID}" 2>/dev/null || CODEMAP_ENABLED="false"
if [ "$CODEMAP_ENABLED" = "true" ]; then
codemap-py query central --top 5 2>/dev/null
fi
After reading traceback or $ARGUMENTS, derive TARGET_MODULE: strip src/, .py suffix, replace / with . (e.g. src/mypackage/auth.py → mypackage.auth); capture the failing function name too, if known, as FAILING_FN. TARGET_MODULE is a substitution token — resolve into the shell variable before the block below. Do NOT execute with literal <TARGET_MODULE> — bash would interpret < as stdin redirect:
# resolve TARGET_MODULE/FAILING_FN first, e.g. TARGET_MODULE=mypackage.auth; FAILING_FN=validate
# timeout: 10000
if [ -z "$TARGET_MODULE" ]; then
echo "⚠ TARGET_MODULE not resolved — skipping codemap rdeps/fn-blast query"
else
codemap-py query rdeps "$TARGET_MODULE" 2>/dev/null
[ -n "$FAILING_FN" ] && codemap-py query fn-blast "$TARGET_MODULE::$FAILING_FN" 2>/dev/null # v3 index only
fi
If codemap-py results returned: prepend ## Structural Context (codemap-py) block to foundry:sw-engineer spawn prompt (Step 1). Callers of failing module = likely affected paths to verify after fix. fn-blast shows transitive callers — high-depth callers are regression risk.
Issue-number mode first — if $ARGUMENTS is issue number, fetch issue body and extract test path BEFORE invoking pytest:
# timeout: 6000
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
# wrapper: cross-repo branch from dev-upstream, persists body to dev-issue-body-${CSID} for next block (avoids re-running gh)
python "${CLAUDE_PLUGIN_ROOT:-plugins/cc_develop}/bin/dev_issue_fetch_wrap.py" debug "$ARGUMENTS"
# timeout: 5000
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
# grep file directly — `read -r` would capture only the first line of a multi-line issue body
TEST_PATH=$(grep -oE '(tests?/[^[:space:]]+\.py|test_[^[:space:]]+\.py)' "${TMPDIR:-/tmp}/dev-issue-body-${CSID}" 2>/dev/null | head -1)
if [ -z "$TEST_PATH" ]; then
echo "→ No test file found in issue; running full test suite"
elif [ ! -f "$TEST_PATH" ]; then
echo "⚠ test path from issue not found on disk: $TEST_PATH — running full suite"
TEST_PATH=""
fi
echo "$TEST_PATH" > "${TMPDIR:-/tmp}/dev-debug-test-path-${CSID}" # persist — three later blocks consume it
Run pytest with extracted path (empty $TEST_PATH → full suite). $TEST_PATH stays unquoted so an empty value collapses to no argument rather than an empty one:
# timeout: 600000
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
IFS= read -r PYTEST_CMD < "${TMPDIR:-/tmp}/dev-pytest-cmd-${CSID}" 2>/dev/null || PYTEST_CMD=""
IFS= read -r TEST_PATH < "${TMPDIR:-/tmp}/dev-debug-test-path-${CSID}" 2>/dev/null || TEST_PATH=""
if [ -z "$PYTEST_CMD" ]; then
echo "! PYTEST_CMD unresolved — re-run §Project Detection (runner-detection.md); an empty command would exit 127 and be misread as a reproduced bug"
else
$PYTEST_CMD --tb=long ${TEST_PATH} -v 2>&1 | tail -60
GATE_EXIT=${PIPESTATUS[0]}
echo "$GATE_EXIT" > "${TMPDIR:-/tmp}/dev-gate-exit-${CSID}"
if [ "$GATE_EXIT" -ne 0 ]; then
echo "Bug reproduced — tests fail. Proceed to fix."
else
echo "Tests pass — bug may not be reproducible via pytest; check symptom directly."
fi
fi
# timeout: 3000
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
IFS= read -r TEST_PATH < "${TMPDIR:-/tmp}/dev-debug-test-path-${CSID}" 2>/dev/null || TEST_PATH=""
git log --oneline -20
COMMIT_COUNT=$(git rev-list --count HEAD 2>/dev/null || echo 1)
LOOKBACK=$(( COMMIT_COUNT < 5 ? COMMIT_COUNT : 5 ))
if [ "$LOOKBACK" -gt 1 ]; then
# empty pathspec is fatal (exit 128) — omit `--` clause for full-repo diff
if [ -n "$TEST_PATH" ]; then
git diff "HEAD~${LOOKBACK}..HEAD" -- "$TEST_PATH"
else
git diff "HEAD~${LOOKBACK}..HEAD"
fi
fi
Cross-repo adaptation (when REPO_NAME set) — issue from different codebase. After fetching issue:
- Extract bug's root cause intent — what invariant violated, not just described symptoms (which may reference upstream structure or code paths)
- Search LOCAL codebase for equivalent failure site — grep for related symbols; code paths may differ from upstream due to divergence
- Treat upstream issue as debugging context, not as a map — trace actual failure in local code
Symptom-text mode — if $ARGUMENTS is free-text, skip issue fetch + extraction; locate failing test path from symptom directly. <test_path> is a substitution token — resolve into shell variable $TEST_PATH first (via Grep against symptom keywords or heuristic file search), then use $TEST_PATH in pytest call. Do NOT execute with literal <test_path> string — bash would interpret < as stdin redirect from a file named test_path:
# timeout: 600000
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
IFS= read -r PYTEST_CMD < "${TMPDIR:-/tmp}/dev-pytest-cmd-${CSID}" 2>/dev/null || PYTEST_CMD=""
TEST_PATH="" # REPLACE with the resolved failing test path, e.g. $(grep -rlE '<symptom keyword>' tests/ --include='*.py' | head -1); empty → full suite
echo "$TEST_PATH" > "${TMPDIR:-/tmp}/dev-debug-test-path-${CSID}" # persist — later blocks consume it
if [ -z "$PYTEST_CMD" ]; then
echo "! PYTEST_CMD unresolved — re-run §Project Detection (runner-detection.md); an empty command would exit 127 and be misread as a reproduced bug"
else
$PYTEST_CMD --tb=long ${TEST_PATH} -v 2>&1 | tail -60
GATE_EXIT=${PIPESTATUS[0]}
echo "$GATE_EXIT" > "${TMPDIR:-/tmp}/dev-gate-exit-${CSID}"
if [ "$GATE_EXIT" -ne 0 ]; then
echo "Bug reproduced — tests fail. Proceed to fix."
else
echo "Tests pass — bug may not be reproducible via pytest; check symptom directly."
fi
fi
Claim-validation gate — before debugging, validate that user's expectation is itself correct. A bug report always contains an implicit or explicit claim: "X should behave like Y". Claim may be wrong — misread docs, misunderstood API contract, incorrect formula, outdated assumption. Fixing a correct implementation to match a wrong expectation wastes effort and introduces regressions.
Classify claim type and validate accordingly:
| Claim type | Example | Validation approach |
|---|---|---|
| Numeric / metric result | "IoU should be 0.5 but returns 0.3" | Verify formula from authoritative source; compute expected value independently |
| API contract | "function should return list but returns generator" | Read docstring, type hints, and docs — not current implementation |
| Algorithm correctness | "sorting is wrong — element 3 should come before element 1" | Trace comparison logic against documented sort key or invariant |
| Behavioral invariant | "adding item twice should raise, not silently dedupe" | Check README, docs, or published contract — not assumed behavior |
| Cross-version assumption | "this worked in v1, now broken" | Check changelog/release notes for intentional breaking change |
| Domain-specific formula | ML metric, statistical estimator, signal processing | Spawn research:scientist (requires research plugin); pass metric name, formula used, claimed expected value; ask: "Is the claimed expected value correct per authoritative definition?" |
Resolution rules:
- Claim verifiable from docs/type hints/tests → read source now; confirm before proceeding
- Claim verifiable by quick computation → run inline script to compute expected value independently
- Domain-specific claim requiring literature → spawn
research:scientist; if plugin absent flag:⚠ Expected value unverified — treating as assumption - Claim contradicts docs/contract → it is a documentation misunderstanding, not a bug; surface this to user before any code change
- Gate: do not form root-cause hypothesis until claimed expectation confirmed or explicitly flagged as unverified; wrong expectation → wrong fix
Use Grep (pattern: failing symbol, class, or error keyword) to trace call path from entry point to failure site. Path hint: use src/ if exists, else search from project root (.).
Spawn foundry:sw-engineer agent to map execution path and produce:
- Entry point to failure: which modules does call cross?
- What state mutated along the way?
- What invariant violated at failure point?
- Any recent commit touching this path (from git log output)
Scope gate: if root cause spans 3+ modules, flag complexity smell. Use AskUserQuestion to present scope concern before proceeding, with options: "Narrow scope (Recommended)" / "Proceed anyway".
Present agent's analysis summary before proceeding.
Flaky-test branch — if symptom is intermittent (passes alone, fails in full suite): run binary-search isolation. <failing-test-node-id> is a substitution token — before executing this block, resolve failing test node ID from $ARGUMENTS or from prior pytest output (captured in a shell variable, e.g. FAILING_TEST_NODE=tests/foo.py::test_bar), then substitute literal node ID into command. Do NOT execute with literal <failing-test-node-id> string — bash would interpret < as stdin redirect:
# resolve FAILING_TEST_NODE first (bash reads literal <...> as redirect):
# FAILING_TEST_NODE=$(echo "$ARGUMENTS" | grep -oE 'tests?/[^[:space:]]+::test_[^[:space:]]+' | head -1)
if [ -z "$FAILING_TEST_NODE" ]; then
echo "⚠ FAILING_TEST_NODE not resolved — cannot run polluter isolation; surface failing test node ID first"
else
python "${CLAUDE_PLUGIN_ROOT:-plugins/cc_develop}/bin/find-polluter.py" "$FAILING_TEST_NODE" # timeout: 60000
fi
Output names polluting upstream test. find-polluter.py ships in this plugin's own bin/ (kept identical to foundry's canonical by propagate_shared.py), so this works on a develop-only install. Run only when CI shows non-deterministic failure pattern.
Step 2: Pattern analysis
Find nearest similar working code path, compare exhaustively:
Shortened here. Read the whole file on GitHub.
Signals
- GitHub stars
- 27
- Forks
- 4
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
debug-borda- Source
- github.com/borda/ai-rig