{"id":"d657e945-3dde-4392-bf70-e1b0a6610e3d","shortId":"xAw6zS","kind":"skill","title":"bisect","tagline":"Use when hunting a regression, phrases like \"bisect\", \"find the commit that broke X\", \"this used to work\", \"regression in test Y\", \"when did <symptom> start\". Also use when escalated from ci-debug-loop because log analysis can't pinpoint the offending change, or when a previously","description":"# Bisect\n\nDrive `git bisect` to isolate the first commit that introduced a bug, then report it. The skill stops at the report. Fixing belongs to the user with full context.\n\n## When to use\n\n- User asks to \"bisect\", \"find the commit that broke X\", says \"this used to work\", or names a regression in a specific test/build target.\n- `ci-debug-loop` exhausted log analysis without identifying a clear cause.\n- A behavior, test, or build that was green at a known earlier point is now red.\n\nDo **not** use when the bug is obviously in the most recent commit (inspect it), or when the reproducer is too fragile/flaky to drive an automated bisect (fix the flake first).\n\n## Inputs (confirm before starting)\n\nPer `confirm-before-implementing`, gather these before touching `git bisect`:\n\n- **Symptom**: one-line description of what's broken (e.g. \"TestExchangeToken fails with `unauthorized`\").\n- **Reproducer**: a shell command that exits 0 when the bug is absent, 1 when present. If the user can't supply one, build it together (see [Reproducer strategy](#reproducer-strategy)).\n- **Known-bad ref**: default `HEAD`.\n- **Known-good ref**: default oldest commit within the last 2 weeks:\n  ```sh\n  git rev-list --since='2 weeks ago' --reverse HEAD | head -1\n  ```\n  If the user has a tag, branch, or specific date in mind, use it instead.\n\n## Reproducer strategy\n\nThe reproducer must work at *every* commit in the bisect range. The reproducer that exists today often won't: APIs change, test files don't yet exist on old commits, etc. Pick the highest-applicable strategy:\n\n1. **External reproducer (preferred).** A self-contained shell script in `$TMPDIR` (e.g. `/tmp/repro.sh`) that builds and exercises the project from outside the source tree. Hits a stable surface (CLI flag, HTTP endpoint, exported function with stable signature) that survived the bisect range. Examples:\n   ```sh\n   # CLI: build and check observable behavior\n   go build -o /tmp/foo ./cmd/foo && /tmp/foo --some-flag | grep -q expected\n   # HTTP: spin up server, hit endpoint, kill\n   go run ./cmd/server & PID=$!; sleep 1; curl -fsS localhost:8080/health; kill $PID\n   ```\n   This survives any in-tree changes because the reproducer doesn't live in the tree.\n\n2. **Carry an in-tree reproducer file** across checkouts. Stash the reproducer test file, `git stash apply` it at each step, run, undo. Workable but brittle; checkouts during bisect can conflict with the stash. Use only when an external reproducer is impossible.\n\n3. **Narrow the range first.** If the reproducer fundamentally requires an API that didn't exist before commit X, set the known-good to X and bisect within the API-stable window. The bisect then can't find regressions older than X. Accept that as a scope limit, don't fight it.\n\nIf none of the three apply, bisect is the wrong tool. Read the diff manually instead.\n\n## Workflow\n\n1. **Resolve refs and reproducer.** Confirm the reproducer exits 1 at HEAD (bug present) and 0 at known-good (bug absent). If either fails, the inputs are wrong. Stop and re-gather.\n\n2. **Estimate cost.** Count commits in range:\n   ```sh\n   git rev-list --count <good>..<bad>\n   ```\n   Steps ≈ `log2(N)`. If more than ~8 steps (>256 commits), ask the user to narrow the known-good; bisect time grows quickly.\n\n3. **Snapshot working state.** Auto-stash uncommitted changes including untracked files:\n   ```sh\n   STASH_REF=\"\"\n   if ! git diff --quiet || ! git diff --cached --quiet || [ -n \"$(git ls-files --others --exclude-standard)\" ]; then\n     git stash push -u -m \"bisect-auto-$(date +%s)\" && STASH_REF=$(git rev-parse stash@{0})\n   fi\n   ORIG_HEAD=$(git rev-parse --abbrev-ref HEAD)\n   trap 'git bisect reset >/dev/null 2>&1; git checkout \"$ORIG_HEAD\" 2>/dev/null; [ -n \"$STASH_REF\" ] && git stash pop 2>/dev/null' EXIT INT TERM\n   ```\n   The trap restores state on success, error, or interrupt.\n\n4. **Build the wrapper script.** Write to `$TMPDIR/bisect-wrapper.sh`:\n   ```sh\n   #!/bin/sh\n   set -e\n\n   # Skip commits explicitly marked as intentionally broken.\n   if git log -1 --format=%B | grep -qF '[skip-bisect]'; then\n     exit 125\n   fi\n\n   # Optional project regen, uncomment per project needs.\n   # task generate || exit 125\n   # buf generate || exit 125\n\n   # Run the reproducer. Exit 0 = good, 1 = bad, 125 = skip.\n   <USER REPRODUCER COMMAND HERE>\n   ```\n   `chmod +x` the wrapper. Compile errors and missing-tool failures from the reproducer should propagate as exit codes the wrapper passes through; commits that can't even build typically return non-zero from the build step. Convert those to 125 by guarding with `|| exit 125` if the user wants to skip them rather than treat them as bad.\n\n5. **Run bisect.**\n   ```sh\n   git bisect start <bad> <good>\n   git bisect run \"$TMPDIR/bisect-wrapper.sh\"\n   ```\n   `git bisect run` drives to completion automatically. Capture the output.\n\n6. **Read the result.** The last line of `git bisect run` output is `<sha> is the first bad commit`. Capture the SHA:\n   ```sh\n   FIRST_BAD=$(git bisect log | awk '/first bad commit/ {print $2}' | head -1)\n   ```\n   If `git bisect log` doesn't have it (older git), parse the run output instead.\n\n7. **Reset state.** `git bisect reset` returns HEAD to the original ref. The trap will fire on exit and restore the stash.\n\n8. **Generate report** (see [Output](#output)).\n\n## Edge cases\n\n- **Flaky reproducer.** If the same commit returns different exit codes on repeat runs, bisect is unreliable. Loop the reproducer N times (e.g. 5) and treat *any* failure as bad. Wrap the user's command in `for i in 1 2 3 4 5; do <repro> || exit 1; done; exit 0`. If still flaky, fix the flake first.\n- **Stale generated files** after `git checkout` between steps. Add a regen step at the top of the wrapper (`task generate`, `buf generate`, `npm run generate`) before the reproducer.\n- **`[skip-bisect]` commits.** Wrapper greps the message and exits 125. This relies on the `commit-per-phase` rule's marker convention; assume well-formed history.\n- **Compile/setup failures** on old commits often surface as non-zero exits. By default `git bisect run` treats those as \"bad\". If they're really \"can't tell\", convert to 125 by guarding setup with `|| exit 125` in the wrapper.\n- **Working tree dirt.** Handled by step 3's auto-stash + trap. Don't skip the trap; Ctrl-C without it leaves the user mid-bisect.\n- **Merges in the range.** `git bisect` handles them by linearizing via the commit graph. No special action.\n- **Refactor renames.** The offending commit may be a rename or move that exposes a latent bug elsewhere. Note this in the report; don't claim the rename is the root cause.\n\n## Output\n\nProduce a single Markdown report:\n\n```markdown\n## Offending commit\n**<sha7>** — <subject>\nAuthor: <name> | Date: <YYYY-MM-DD>\nPR: <url if found via `gh pr list --search \"<sha>\" --state merged`>\n\n## Diff\n<output of `git show --stat <sha>` followed by `git show <sha>`>\n\n## Commit message\n<full body from `git log -1 --format=%B <sha>`>\n\n## Suggested next step\n- [ ] Write a regression test that reproduces <symptom> at HEAD (so the bisect is repeatable)\n- [ ] Revert + reimplement, OR fix forward (user's call)\n- [ ] Open issue / PR comment if the offending commit was already merged and shipped\n```\n\nDo **not** auto-revert, auto-fix, or push anything. The skill ends at the report.\n\n## Cross-references\n\n- `commit-per-phase` rule defines the `[skip-bisect]` marker the wrapper looks for.\n- `ci-debug-loop` skill is the typical escalation source. When log analysis stalls, hand the failing test/command to this skill as the reproducer.\n- `verify-when-complete` skill produces good reproducer one-liners (`task test -- -run '^TestX$'`).\n\n## Anti-patterns\n\n- Running bisect without a deterministic reproducer. Guesses propagate exponentially.\n- Skipping the auto-stash and trap. Leaves the user mid-bisect on Ctrl-C.\n- Treating compile-error commits as \"bad\" without checking. That's a setup failure, not the bug.\n- Auto-fixing the offending commit. Out of scope for this skill.\n- Per `~/.claude/rules/probe-not-assume.md`: confirm via tool/command before recommending; do not infer.","tags":["bisect","skill","issue","paultyng","agent-skills","ai-tools","claude-code","cursor","dotfiles"],"capabilities":["skill","source-paultyng","skill-bisect","topic-agent-skills","topic-ai-tools","topic-claude-code","topic-cursor","topic-dotfiles"],"categories":["skill-issue"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/paultyng/skill-issue/bisect","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add paultyng/skill-issue","source_repo":"https://github.com/paultyng/skill-issue","install_from":"skills.sh"}},"qualityScore":"0.454","qualityRationale":"deterministic score 0.45 from registry signals: · indexed on github topic:agent-skills · 8 github stars · SKILL.md body (8,138 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-05-18T19:09:00.105Z","embedding":null,"createdAt":"2026-05-18T13:21:24.690Z","updatedAt":"2026-05-18T19:09:00.105Z","lastSeenAt":"2026-05-18T19:09:00.105Z","tsv":"'-1':255,709,861,1174 '/.claude/rules/probe-not-assume.md':1347 '/bin/sh':696 '/cmd/foo':365 '/cmd/server':382 '/dev/null':658,666,674 '/first':855 '/tmp/foo':364,366 '/tmp/repro.sh':323 '0':200,537,642,740,955 '1':206,310,385,522,531,660,742,945,952 '125':719,731,735,744,787,792,1001,1049,1055 '2':241,249,408,556,659,665,673,859,946 '256':577 '3':451,592,947,1065 '4':687,948 '5':806,929,949 '6':827 '7':877 '8':575,899 '8080/health':389 'abbrev':651 'abbrev-ref':650 'absent':205,543 'accept':495 'across':416 'action':1103 'add':971 'ago':251 'alreadi':1210 'also':27 'analysi':38,112,1261 'anti':1289 'anti-pattern':1288 'anyth':1224 'api':292,462,482 'api-st':481 'appli':425,510 'applic':308 'ask':83,579 'assum':1014 'author':1144 'auto':597,632,1068,1217,1220,1303,1335 'auto-fix':1219,1334 'auto-revert':1216 'auto-stash':596,1067,1302 'autom':159 'automat':823 'awk':854 'b':711,1176 'bad':227,743,805,843,850,856,935,1039,1323 'behavior':119,360 'belong':72 'bisect':1,9,49,52,85,160,179,282,351,437,478,486,511,588,631,656,716,808,811,814,818,836,852,864,881,920,993,1034,1086,1092,1190,1243,1292,1312 'bisect-auto':630 'bodi':1170 'branch':262 'brittl':434 'broke':14,90 'broken':188,705 'buf':732,983 'bug':61,139,203,534,542,1119,1333 'build':122,216,325,356,362,688,774,782 'c':1078,1316 'cach':613 'call':1200 'captur':824,845 'carri':409 'case':906 'caus':117,1134 'chang':44,293,398,600 'check':358,1325 'checkout':417,435,662,968 'chmod':746 'ci':33,107,1250 'ci-debug-loop':32,106,1249 'claim':1128 'clear':116 'cli':339,355 'code':764,916 'command':197,940 'comment':1204 'commit':12,57,88,146,237,279,302,468,560,578,700,769,844,857,912,994,1007,1023,1099,1108,1143,1167,1208,1235,1321,1339 'commit-per-phas':1006,1234 'compil':750,1319 'compile-error':1318 'compile/setup':1019 'complet':822,1276 'confirm':166,171,527,1348 'confirm-before-impl':170 'conflict':439 'contain':317 'context':78 'convent':1013 'convert':784,1047 'cost':558 'count':559,568 'cross':1232 'cross-refer':1231 'ctrl':1077,1315 'ctrl-c':1076,1314 'curl':386 'date':265,633,1145 'debug':34,108,1251 'default':229,235,1032 'defin':1239 'descript':184 'determinist':1295 'didn':464 'diff':518,609,612,1157 'differ':914 'dirt':1061 'doesn':402,866 'done':953 'drive':50,157,820 'e':698 'e.g':189,322,928 'earlier':129 'edg':905 'either':545 'elsewher':1120 'end':1227 'endpoint':342,378 'error':684,751,1320 'escal':30,1257 'estim':557 'etc':303 'even':773 'everi':278 'exampl':353 'exclud':622 'exclude-standard':621 'exercis':327 'exhaust':110 'exist':287,299,466 'exit':199,530,675,718,730,734,739,763,791,894,915,951,954,1000,1030,1054 'expect':372 'explicit':701 'exponenti':1299 'export':343 'expos':1116 'extern':311,447 'fail':191,546,1265 'failur':756,933,1020,1330 'fi':643,720 'fight':503 'file':295,415,422,603,619,965 'find':10,86,490 'fire':892 'first':56,164,455,842,849,962 'fix':71,161,959,1196,1221,1336 'flag':340,369 'flake':163,961 'flaki':907,958 'follow':1163 'form':1017 'format':710,1175 'forward':1197 'found':1149 'fragile/flaky':155 'fss':387 'full':77,1169 'function':344 'fundament':459 'gather':174,555 'generat':729,733,900,964,982,984,987 'gh':1151 'git':51,178,244,423,564,608,611,616,625,637,646,655,661,670,707,810,813,817,835,851,863,871,880,967,1033,1091,1160,1165,1172 'go':361,380 'good':233,474,541,587,741,1279 'graph':1100 'green':125 'grep':370,712,996 'grow':590 'guard':789,1051 'guess':1297 'hand':1263 'handl':1062,1093 'head':230,253,254,533,645,653,664,860,884,1187 'highest':307 'highest-applic':306 'histori':1018 'hit':335,377 'http':341,373 'hunt':4 'identifi':114 'implement':173 'imposs':450 'in-tre':395,411 'includ':601 'infer':1355 'input':165,548 'inspect':147 'instead':270,520,876 'int':676 'intent':704 'interrupt':686 'introduc':59 'isol':54 'issu':1202 'kill':379,390 'known':128,226,232,473,540,586 'known-bad':225 'known-good':231,472,539,585 'last':240,832 'latent':1118 'leav':1081,1307 'like':8 'limit':500 'line':183,833 'linear':1096 'liner':1283 'list':247,567,1153 'live':404 'localhost':388 'log':37,111,708,853,865,1173,1260 'log2':570 'look':1247 'loop':35,109,923,1252 'ls':618 'ls-file':617 'm':629 'manual':519 'mark':702 'markdown':1139,1141 'marker':1012,1244 'may':1109 'merg':1087,1156,1211 'messag':998,1168 'mid':1085,1311 'mid-bisect':1084,1310 'mind':267 'miss':754 'missing-tool':753 'move':1114 'must':275 'n':571,615,667,926 'name':98 'narrow':452,583 'need':727 'next':1178 'non':778,1028 'non-zero':777,1027 'none':506 'note':1121 'npm':985 'o':363 'observ':359 'obvious':141 'offend':43,1107,1142,1207,1338 'often':289,1024 'old':301,1022 'older':492,870 'oldest':236 'one':182,215,1282 'one-lin':181,1281 'open':1201 'option':721 'orig':644,663 'origin':887 'other':620 'output':826,838,875,903,904,1135,1158 'outsid':331 'pars':640,649,872 'pass':767 'pattern':1290 'per':169,725,1008,1236,1346 'phase':1009,1237 'phrase':7 'pick':304 'pid':383,391 'pinpoint':41 'point':130 'pop':672 'pr':1146,1152,1203 'prefer':313 'present':208,535 'previous':48 'print':858 'produc':1136,1278 'project':329,722,726 'propag':761,1298 'push':627,1223 'q':371 'qf':713 'quick':591 'quiet':610,614 'rang':283,352,454,562,1090 'rather':800 're':554,1042 're-gath':553 'read':516,828 'realli':1043 'recent':145 'recommend':1352 'red':133 'ref':228,234,524,606,636,652,669,888 'refactor':1104 'refer':1233 'regen':723,973 'regress':6,20,100,491,1182 'reimplement':1194 'reli':1003 'renam':1105,1112,1130 'repeat':918,1192 'report':63,70,901,1125,1140,1230 'reproduc':152,194,220,223,271,274,285,312,401,414,420,448,458,526,529,738,759,908,925,990,1185,1272,1280,1296 'reproducer-strategi':222 'requir':460 'reset':657,878,882 'resolv':523 'restor':680,896 'result':830 'return':776,883,913 'rev':246,566,639,648 'rev-list':245,565 'rev-pars':638,647 'revers':252 'revert':1193,1218 'root':1133 'rule':1010,1238 'run':381,430,736,807,815,819,837,874,919,986,1035,1286,1291 'say':92 'scope':499,1342 'script':319,691 'search':1154 'see':219,902 'self':316 'self-contain':315 'server':376 'set':470,697 'setup':1052,1329 'sh':243,354,563,604,695,809,848 'sha':847 'shell':196,318 'ship':1213 'show':1161,1166 'signatur':347 'sinc':248 'singl':1138 'skill':66,1226,1253,1269,1277,1345 'skill-bisect' 'skip':699,715,745,798,992,1073,1242,1300 'skip-bisect':714,991,1241 'sleep':384 'snapshot':593 'some-flag':367 'sourc':333,1258 'source-paultyng' 'special':1102 'specif':103,264 'spin':374 'stabl':337,346,483 'stale':963 'stall':1262 'standard':623 'start':26,168,812 'stash':418,424,442,598,605,626,635,641,668,671,898,1069,1304 'stat':1162 'state':595,681,879,1155 'step':429,569,576,783,970,974,1064,1179 'still':957 'stop':67,551 'strategi':221,224,272,309 'success':683 'suggest':1177 'suppli':214 'surfac':338,1025 'surviv':349,393 'symptom':180 'tag':261 'target':105 'task':728,981,1284 'tell':1046 'term':677 'test':22,120,294,421,1183,1285 'test/build':104 'test/command':1266 'testexchangetoken':190 'testx':1287 'three':509 'time':589,927 'tmpdir':321 'tmpdir/bisect-wrapper.sh':694,816 'today':288 'togeth':218 'tool':515,755 'tool/command':1350 'top':977 'topic-agent-skills' 'topic-ai-tools' 'topic-claude-code' 'topic-cursor' 'topic-dotfiles' 'touch':177 'trap':654,679,890,1070,1075,1306 'treat':802,931,1036,1317 'tree':334,397,407,413,1060 'typic':775,1256 'u':628 'unauthor':193 'uncom':724 'uncommit':599 'undo':431 'unreli':922 'untrack':602 'url':1147 'use':2,17,28,81,94,136,268,443 'user':75,82,211,258,581,795,938,1083,1198,1309 'verifi':1274 'verify-when-complet':1273 'via':1097,1150,1349 'want':796 'week':242,250 'well':1016 'well-form':1015 'window':484 'within':238,479 'without':113,1079,1293,1324 'won':290 'work':19,96,276,594,1059 'workabl':432 'workflow':521 'wrap':936 'wrapper':690,749,766,980,995,1058,1246 'write':692,1180 'wrong':514,550 'x':15,91,469,476,494,747 'y':23 'yet':298 'zero':779,1029","prices":[{"id":"e4959c71-4307-4036-b91a-463ad571e99d","listingId":"d657e945-3dde-4392-bf70-e1b0a6610e3d","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"paultyng","category":"skill-issue","install_from":"skills.sh"},"createdAt":"2026-05-18T13:21:24.690Z"}],"sources":[{"listingId":"d657e945-3dde-4392-bf70-e1b0a6610e3d","source":"github","sourceId":"paultyng/skill-issue/bisect","sourceUrl":"https://github.com/paultyng/skill-issue/tree/main/skills/bisect","isPrimary":false,"firstSeenAt":"2026-05-18T13:21:24.690Z","lastSeenAt":"2026-05-18T19:09:00.105Z"}],"details":{"listingId":"d657e945-3dde-4392-bf70-e1b0a6610e3d","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"paultyng","slug":"bisect","github":{"repo":"paultyng/skill-issue","stars":8,"topics":["agent-skills","ai-tools","claude-code","cursor","dotfiles"],"license":"mit","html_url":"https://github.com/paultyng/skill-issue","pushed_at":"2026-05-18T18:26:54Z","description":"Personal Claude Code / Cursor agent skills, rules, and config","skill_md_sha":"9abc950bcd5aba8d351295958d95596294a24cf1","skill_md_path":"skills/bisect/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/paultyng/skill-issue/tree/main/skills/bisect"},"layout":"multi","source":"github","category":"skill-issue","frontmatter":{"name":"bisect","description":"Use when hunting a regression, phrases like \"bisect\", \"find the commit that broke X\", \"this used to work\", \"regression in test Y\", \"when did <symptom> start\". Also use when escalated from ci-debug-loop because log analysis can't pinpoint the offending change, or when a previously-passing test/build/behavior is now failing and the cause isn't obvious from the recent diff. Drives `git bisect run` against a user-provided reproducer command, isolates the offending commit, and reports it with diff and metadata. Does not auto-fix."},"skills_sh_url":"https://skills.sh/paultyng/skill-issue/bisect"},"updatedAt":"2026-05-18T19:09:00.105Z"}}