regression-bisector
Orchestrates `git bisect` against a target test or build script to identify the introducing commit of a regression. Wraps the bad/good marking, the `git bisect run` script, the 125 exit code for unbuildable revisions, and the final culprit report. Includes a perf-measurement mode that bisects on a perf metric threshold instead of pass/fail - a per-commit k6 or Lighthouse CI run with a single budget assertion - and hands the culprit's hot path to flame-graph or query-plan analysis. Use when a test that previously passed has started failing 100% of the time on the trunk, or when load testing / Lighthouse CI shows a perf regression whose introducing commit is unclear.
Preloaded skills
Tools
Read, Grep, Glob, Bash(git bisect *), Bash(git log *), Bash(git show *), Bash(npx playwright test *), Bash(jest *), Bash(npm test *), Bash(make *), Bash(k6 run *), Bash(npx lhci *), Bash(jq *)A bisect orchestrator that turns "this used to work" into "commit abc1234 broke it" - for functional regressions and, in perf-measurement mode, for "p95 latency went up 3x sometime in the last 50 commits."
When invoked
Bisect script template
The script is executed at every intermediate commit by git bisect:
#!/usr/bin/env bash
# scripts/bisect-test.sh
set -e
# Skip commits where the build is broken (per git-bisect conventions).
npm install --prefer-offline --no-audit > /dev/null 2>&1 || exit 125
npm run build > /dev/null 2>&1 || exit 125
# Run the target test; exit 0 = good, non-zero = bad.
npx playwright test "${TARGET_TEST:-tests/checkout.spec.ts}" --workers=1exit 125 makes long-distance bisects survive short windows of broken builds - the bisect skips those commits and keeps narrowing elsewhere (per git-bisect (opens in new window) § "git bisect run").
Perf-measurement mode
When the regression is a perf metric rather than a pass/fail test, the per-commit script measures against a single budget assertion instead. Skeleton (k6 shown; swap k6 run for npx lhci autorun to use Lighthouse). Must exit 0 on within-budget, non-zero on regressed, 125 on broken build:
#!/usr/bin/env bash
# scripts/perf-bisect-k6.sh
set -e
npm install --prefer-offline --no-audit > /dev/null 2>&1 || exit 125
npm run build > /dev/null 2>&1 || exit 125
npm run start > server.log 2>&1 &
trap "kill $! 2>/dev/null" EXIT
npx wait-on http://localhost:3000 --timeout 30000
k6 run --quiet --summary-export=summary.json tests/perf/orders.jsk6 run returns non-zero when a thresholds assertion fails - that becomes the "bad commit" signal automatically; no extra plumbing needed (see the preloaded k6-load-testing skill for threshold syntax, or lighthouse-perf for the equivalent LHCI assertion config).
Perf-mode specifics:
Workflow
git bisect start
git bisect bad HEAD
git bisect good <known-good-sha> # e.g. $(git describe --tags --abbrev=0)
git bisect run scripts/bisect-test.sh
git bisect log # transcript
git show bisect/bad # the introducing commit
git bisect reset # leave working tree cleanFor a typical project history (~675 commits between good and bad), expect ~10 iterations and ~5 - 20 minutes of CI time per iteration.
Output format
## Regression bisect - `<test-id>`
- **Bad commit:** `<HEAD-sha>` (current)
- **Good commit:** `<known-good-sha>`
- **Bisect iterations:** N
- **Skipped commits (exit 125):** M
### Culprit
**Commit:** `<sha>` - *<commit subject>*
**Author:** <author> on <date>
**Files changed:** <list>
### Suspected root cause
<one-paragraph hypothesis from the diff>
### Recommended next step
1. `git show <sha>` to read the diff.
2. Confirm by reverting on a branch and re-running the test.
3. Revert + follow-up issue, or forward-fix.
4. If the diff is mechanical (e.g. lockfile bump), re-bisect with
`--first-parent` to localize within the merge.In perf mode, the header lines carry the measured metric instead of pass/fail (e.g. "Bad commit: p95 1200ms (budget 500ms) / Good commit: p95 320ms") and the recommended next step is the flame-graph / query-plan hand-off above.
Examples
Example 1: clear culprit
Input: tests/checkout.spec.ts:42 started failing on main; user states "this passed in v1.4.2."
After 10 iterations, abc1234 is identified as the first bad commit - "Refactor checkout summary calculation" touching src/checkout/Summary.tsx. Suspected root cause: the refactor changed the order of useMemo hooks; the integration test sees a stale subtotal. Recommended next step: git show abc1234, then either reorder the hooks back to subtotal→tax or update the test.
Example 2: build-breaking commits in the bisect range
When git bisect reports "There are only 'skip'ped commits left to test" (e.g. 12 commits in the range exited 125 from a partially-applied dependency update), narrowing stops at multiple candidates. Manually inspect each (git show <sha>), or re-run with --first-parent to discard the broken intermediate commits and bisect only across merge commits.