| name | debug-issue |
| description | Drive one PrairieLearn GitHub issue or PR end-to-end through the harness — PRE-FLIGHT → GATHER → SCAN → CLASSIFY → BUILD. Spawns an isolated worktree + Docker journey, reproduces the bug with Playwright, injects the trace spine closest to the suspect code, classifies root cause from the merged timeline, then verifies a fix with before/after evidence. Runs inline. Use when the user says "debug issue N", "repro this PR", "investigate |
debug-issue
Orchestrates a full debugging journey for one PrairieLearn issue/PR. One journey at a time — finish and tear down before the next.
Invoke: /debug-issue 12345 · /debug-issue --pr 67890
The five phases (gate between each — no skipping)
1. PRE-FLIGHT — understand before running anything
./scripts/spawn-journey.sh --issue N (or --pr N) — creates the journey + worktree + templates. In a self-contained workspace — a PL tree checked out beside the harness plus a local issue.md, with no GitHub number (a clone, or an isolated eval) — run it with no arguments: it auto-detects local mode and drives the in-workspace PL tree from issue.md, with no PL-main or GitHub dependency. run-journey.sh (no args) then targets that same tree. Reach for the network/PL-main path only when the workspace doesn't carry its own source.
- Read the issue and
journeys/<id>/reproduction.md. Fill it in: problem, reproduce steps, expected vs actual, root-cause hypothesis, and the ring-0 suspect file(s) — the code closest to the bug. That file is where tracing starts.
- Map the symptom to a PL pattern with reference/architecture-patterns.md (request / question / grading / sync lifecycles — what the code does and its seams), then to an injection layer with reference/control-surfaces.md (where to trace).
- GATE: don't run anything until reproduction.md names a hypothesis and a suspect file.
2. GATHER — make the environment reproduce the bug
- Edit
setup.sh to seed the exact broken state (course, users, assessment). Keep it idempotent.
- Confirm the worktree is on the right branch:
git -C <worktree> status.
3. SCAN — reproduce empirically at the smallest sufficient layer
- Run it; don't just read it. Fast inner loop: slice isolation (the slice skill) — pull the suspect function/module out and exercise it with instrumented inputs/intermediates/outputs. Outer loop for anything that needs the real system: the booted stack. Either way inject tracing closest to the issue first (the trace skill):
./scripts/inject-trace.sh --worktree <wt> then ring-0 trace() calls in the suspect file. Run, and read the timeline.
- Pick the smallest layer that exhibits the bug: a unit test/snippet with trace calls for a pure function; the rendered page for a UI bug. For a browser case write
scenario.ts (journey-run skill: persona, render-gate, screenshots) and ./scripts/run-journey.sh --issue N (containers, seed, before screenshot + merged trace).
- GATE: the run produced evidence — a trace line and/or a broken-state screenshot — that shows the reported failure. If it doesn't reproduce, it may be already fixed on current master (verify at a pre-fix commit) — not necessarily a wrong repro. Reasoning without a run does not pass; if it can't run, fix the harness so it can.
4. CLASSIFY — root cause from the timeline
- Read
/tmp/prairielearn-debug/<id>/: screenshots, logs/trace.log (sorted), db-before/after.sql diff.
tsx packages/dev-tools/src/trace-reader.ts <trace.log> --corr <id> to see one request across Node/SQL/Python and find the stall or the swallowed error.
- If ring-0 didn't reveal it, widen the field (trace skill:
--wide / higher ring), re-run. Concentric, not all-at-once.
- GATE: state the root cause in one sentence with evidence (a trace line, a screenshot, a DB diff) before editing code.
5. BUILD — smallest fix in the worktree, verify before/after
- Edit only in the worktree (
/tmp/prairielearn-debug-worktrees/<id>/prairielearn/). The write-guard hook blocks edits to PL main.
- Make the smallest change that removes the symptom — often deleting code, not adding around it. Read the neighboring tests first for invariants you must not break. Smallest ≠ partial: retire code the fix makes redundant and keep one source of truth (trace the consumers of any value you change).
- Verify at the layer you reproduced on (re-run the unit test/snippet, or
run-journey.sh --variant after-fix for a browser case). Confirm the failure is gone and neighbors still pass. If the fix touches a rendered surface in any way — even for a bug that didn't first look visual — capture before/after screenshots as part of this verification (decide by what the fix changes, not the bug's appearance).
- Red-team your own diff against sibling inputs in the same class before trusting it — and confirm it didn't silently drop a side-effect the old code provided: if your fix removes a broad refresh (a full reload, a redirect, a refetch, a global re-render), every surface that mechanism used to update must still update in its place. Enumerate those surfaces; reason about the ones a narrow repro can't exhibit. If the fix changes a DB query, perf-audit it (plan/cost vs base — no new full scan / lost index / N+1) before trusting it.
Output
A short report: hypothesis → repro → root cause (with evidence) → fix → before/after. Always produce a human-readable fix summary alongside the fix — write fix-summary.md into the artifacts dir: plain-language problem, root cause, what changed and why, and the evidence (trace line / screenshot / test), so a maintainer understands it at a glance. By default, stop here — don't create or post a PR/issue. Publishing the fix is a separate, opt-in step: the contribute skill does it fork-only (never upstream) and only on the user's explicit go-ahead; fix-summary.md is its seed.
Cleanup
./scripts/cleanup.sh --issue N (add --keep-artifacts to retain evidence). Then git -C <PL_MAIN> worktree list to confirm it's gone. Remove trace injection from the worktree with inject-trace.sh --revert if you'll keep the branch.
Reflect (always, last)
Close out with the self-improvement reflex (→ rules/generalize.md): what generalized instruction change would help a future blind agent on an unrelated issue? Apply only what generalizes, adds no contradiction, and leaves the set leaner — otherwise change nothing.