agentleFS
intelintel/torch-xpu-ops/.claude/skills/issue-handler/SKILL.md

issue-handler

Skill121 starsChanged 5 months agoApache-2.0View on GitHub

Use when asked to fix a GitHub issue end-to-end, run the agent pipeline on an issue, or process an `agent:active` / batch tracking issue. Orchestrates the full pipeline: `issue-triage` → `fix-reproduce` → `fix-root-cause` → `fix-implement` → `fix-verify` → report. Handles single-bug issues and batch-bug issues (a parent tracking multiple sub-bugs — skip-list or heterogeneous — fanned out one fix branch per sub-bug). On re-run, prioritizes new human feedback and skips work that is still valid.

  • Installs packages

2 other projects use a version of this.

Adopted by

Most recently taken up by

The file

Show its rules and text

We split this skill into the rules it gives an agent

25 rules. About 0 are common, 10 are distinctive, and 15 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈10

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 15

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Every agent-produced diff is a proposal.

    line 22
  2. Distinctive:

    Interactive (default): ask the user when blocked.

    line 106
  3. Very specific:

    Pipeline (explicit): no human to interrupt — advance the issue's agent:status marker, update stage labels, let leaf skills leave their <!-- …

    line 109
  4. Distinctive:

    highest priority signal: Read every such comment verbatim and

    line 183
  5. Distinctive:

    No human pointed anything out since the last agent comment, so re-doing the whole pipeline would just repeat identical work.

    line 194
  6. Distinctive:

    Run Stage 1 (triage) and Stage 2 (reproduce) as normal.

    line 198
  7. Very specific:

    Call issue-triage on the issue body + comments.

    line 226
  8. Very specific:

    Only for the single-bug path (issue_type=single-bug).

    line 248
  9. Distinctive:

    No child GitHub issues are created.

    line 288
  10. Very specific:

    When the list is long (skip-list issues routinely have dozens), front-load the one real wheel install so the per-entry …

    line 338
  11. Distinctive:

    Do not guess which entries share a fix:

    line 358
  12. Very specific:

    Root-cause, then branch: Run Stage 3 (fix-root-cause) first — it returns target_repo, which decides *which* checkout the fix (and thus the …

    line 410
  13. Very specific:

    Create the isolated fix branch on target_repo_dir off its base so the diff is pushable on its own (Branch naming):

    line 421
  14. Very specific:

    On fix-verify PASSED: do NOT push or open a PR. Update the sub-item's fix_result-${slug}.json in place (written at verdict=PENDING_VERIFY …

    line 437
  15. Very specific:

    After the loop, go to Stage 6 Report with the aggregate outcome (IMPLEMENTING(fix_verified) if any sub-item was fixed.

    line 502
  16. Very specific:

    Call fix-root-cause with the failure description and the refined_command from Stage 2.

    line 588
  17. Very specific:

    Call fix-implement with triage_result, pytorch_dir, target_repo_dir (derived from target_repo), and allow_skip:

    line 614
  18. Distinctive:

    If the caller states the flag, the caller wins.

    line 626
  19. Distinctive:

    Do NOT wait for Stage 5 to persist — if verify then crashes or the context runs out, the committed branch would otherwise be an orphan the …

    line 633
  20. Very specific:

    Call fix-verify with refined_command (from Stage 2), target_repo_dir, and changed_files (from Stage 4).

    line 662
  21. Distinctive:

    Always include in the summary:

    line 705
  22. Very specific:

    If the outcome is IMPLEMENTING(fix_verified), the fix is committed on its fix branch (Branch naming) and recorded in fix_result.json.

    line 718
  23. Very specific:

    Every run. The sign-off is outside </details> — it is the comment's footer, not part of the summary:

    line 756
  24. Very specific:

    When the job ends, the fix workflow appends the <!-- agent:review --> block itself — the verdict menu a reviewer copies into a new comment.

    line 804
  25. Distinctive:

    Do not loop on:

    line 824

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Issue Handler — End-to-End Orchestrator

This is the high-level scenario skill for handling a single GitHub issue. It does not do the detailed work itself; it sequences the leaf skills into one iterative pipeline and reports the result. Each stage's mechanics live in its own skill — read and follow that skill when you reach its stage.

Every agent-produced diff is a proposal. This skill commits the staged fix onto a dedicated local branch agent/fix-issue-<N> (single bug) or agent/fix-issue-<N>-<seq>-<slug> (per batch sub-item) and records it in a fix_result*.json. When the bug is owned by an upstream issue — a mirrored pytorch/pytorch#<M> DISABLED test, or a command pointing at an upstream issue — the branch is named after that number instead: agent/fix-pytorch-issue-<M>[-<slug>] (see Branch naming). It never pushes, tags, or opens a PR — the invoking workflow reads each fix_result*.json, exports base_sha..branch as a patch, and a human applies it. The leaves themselves only stage (they never commit).

Contents

  • Pipeline overview
  • Inputs
  • Execution modes
  • Branch naming
  • Stage 0: Re-run gate
  • Stage 1: Triage
  • Stage 2: Reproduce
  • Stage 1u: Batch-issue fan-out (skip-list + heterogeneous)
  • Stage 3: Root cause
  • Stage 4: Implement
  • Stage 5: Verify
  • Stage 6: Report
  • Iterative loop bounds
  • Issue-body status contract

Pipeline overview

Single-bug path (default for issue_type=single-bug):

triage → reproduce → root-cause → implement → verify → report
                       ↑                          |
                       └─── loop up to 3 times ───┘

Batch path (issue_type=batch-bug, either batch_kind) — fan out the single-bug pipeline per sub-item, each on its own fix branch:

triage → [preflight: install nightly wheel once]
       → for each sub-item:
             reset + reproduce
               ├─ NOT_REPRODUCED → heterogeneous: ALREADY_FIXED · skip-list: STALE_SKIP (+follow-up)
               └─ REPRODUCED → branch per [Branch naming](#branch-naming)
                               → root-cause → implement → verify
             (any failure marks the sub-item and continues the batch)
       → fan-out report

skip-list vs heterogeneous differ only in how a NOT_REPRODUCED sub-item is labeled; the loop is identical.

StageLeaf skillPurpose
1. Triageissue-triageText-only classification: single-bug / batch-bug (+ batch_kind) / nonbug, scope, runtime_dependencies, preliminary verdict
2. Reproducefix-reproduceVerify the failure still reproduces against the nightly wheel (stage=nightly)
3. Root causefix-root-causeDeep source analysis, target_repo, domain, IMPLEMENTING/NEEDS_HUMAN
4. Implementfix-implementEdit code, stage the diff (never commit)
5. Verifyfix-verifyRun the refined command against source build, PASSED/FAILED/CANNOT_VERIFY
6. Reportthis skillSummarize outcome to the user (or into the issue in pipeline mode)

Inputs

  • A GitHub issue on intel/torch-xpu-ops or pytorch/pytorch (URL, number, or raw body).
  • pytorch_dir — path to a local pytorch checkout, resolved as described in fix-reproduce Prepare. If absent, this skill lets fix-reproduce / fix-root-cause clone it into $XPU_OPS_ROOT/agent_space_xpu/pytorch/.
  • Mode (see below).

Execution modes

The pipeline runs in one of two modes — interactive (default) or pipeline — which changes how every stage reports results and whether it writes to the GitHub issue. Decide the mode at the start and pass it to every leaf. See references/execution-modes.md for the full contract.

  • Interactive (default): ask the user when blocked; report conversationally; do not touch the issue body / labels / comments unless the user asks.
  • Pipeline (explicit): no human to interrupt — advance the issue's agent:status marker, update stage labels, let leaf skills leave their <!-- agent:<name> --> comments, and stop when the pipeline settles on a terminal verdict.

Branch naming

One rule for both paths: the branch is named after the issue that owns the bug, so a human reading a branch or a patch directory knows which issue a PR closes.

The bug is filed asBranch
an intel/torch-xpu-ops issue #Nagent/fix-issue-<N> (batch sub-item: agent/fix-issue-<N>-<seq>-<slug>)
an upstream pytorch/pytorch#<M> (mirrored DISABLED test, or a command naming an upstream issue)agent/fix-pytorch-issue-<M> (batch sub-item: agent/fix-pytorch-issue-<M>-<slug>)

The upstream form applies whenever the sub-item's own identity is an upstream issue — the common case on the CI DISABLED tracking issue, where #N is only a queue and #M is the bug. #M is already unique and stable, so the upstream form carries no seq. When one fix covers several sub-items, the branch is named after the sub-item that was fixed; the rest are recorded in covers.

M is the upstream issue the sub-item resolves to: the pytorch/pytorch#<M> reference on its checklist line, its linked child reference, or the upstream issue named in the invoking comment. If a sub-item has no upstream number, use the agent/fix-issue-<N>-… form.

A fix spanning both repos (two units) carries this same name in both checkouts — one bug, one name; do not suffix the branch with the repo. The repo shows up in the patch directory instead, which the export step names <branch minus agent/>-<target_repo>: agent/fix-pytorch-issue-197521 in each repo lands in fix-pytorch-issue-197521-pytorch and fix-pytorch-issue-197521-torch-xpu-ops. Two fix_result records naming the same branch and the same target_repo are a naming bug, and the export step fails there rather than overwrite one half (format-patch restarts at 0001 in every directory).

Stage 0 — Re-run gate (first-run vs re-run)

Pipeline mode only; skip in interactive mode (a human is already driving, so just run the full pipeline). An agent:active issue is frequently re-triggered — a maintainer leaves feedback, or the bot is re-invoked with no new information. This gate decides, before spending a build, whether this is a fresh run, a human-feedback re-run (highest priority), or a bare re-run (skip work that already ran and is still valid).

Step 0.1: Detect prior agent activity

Find the most recent agent comment and its timestamp:

last_agent_ts=$(gh issue view "$N" --repo "$OWNER/$REPO" --json comments \
  --jq '[.comments[] | select(.body | test("<!-- agent:"))] | last | .createdAt // ""')

Empty last_agent_ts → first run. Skip the rest of Stage 0 and go to Stage 1 normally.

Step 0.2: Detect new human feedback

A comment is human feedback when it is authored by a non-bot account and created after last_agent_ts. (The bot account is the one that authored the <!-- agent:* --> comments; exclude it by login.)

new_human=$(gh issue view "$N" --repo "$OWNER/$REPO" --json comments \
  --jq --arg ts "$last_agent_ts" --arg bot "$BOT_LOGIN" \
  '[.comments[] | select(.author.login != $bot and .createdAt > $ts)] | length')
  • new_human > 0 → human-feedback re-run. Human feedback is the highest priority signal. Read every such comment verbatim and prepend it to the failure description you hand to Stage 3 (fix-root-cause takes a free-form failure description; a leading "Maintainer feedback since last run: ..." block steers the re-analysis without any new leaf input). Run the full pipeline from Stage 1; do not take any of the skip fast-paths below. A human saying "still wrong" or "change X" overrides any cached verdict.
  • new_human == 0 → bare re-run. Continue to Step 0.3.

Step 0.3: Bare re-run — skip what is still valid

No human pointed anything out since the last agent comment, so re-doing the whole pipeline would just repeat identical work. Re-run only the cheap front of the pipeline and compare against last time:

  1. Run Stage 1 (triage) and Stage 2 (reproduce) as normal.
  2. Compare the reproduce result to the previous run. The previous refined_command + verdict are recoverable from the last <!-- agent:root-cause --> comment's analyzed_sha context, or re-derived by reading the prior <!-- agent:reproduce --> / sweep comment. "Identical" means same verdict and same refined_command.
    • Reproduce differs (now passes, or a different command reproduces) → the situation changed on its own; resume the full pipeline from Stage 3 (root-cause) with the new reproduce result. Do not reuse the cached root-cause.
    • Reproduce identical → nothing observable changed. Hand off to Stage 3, which runs fix-root-cause's own <!-- agent:root-cause --> analyzed_sha fast-path: if target_repo HEAD sha equals the recorded analyzed_sha, that leaf re-emits the prior verdict verbatim and this orchestrator stops (the earlier outcome — fix already staged, or NEEDS_HUMAN — still stands; there is nothing new to do). If the sha moved, fix-root-cause re-analyzes and the pipeline continues from Stage 3 as usual.

This never skips Stage 1/Stage 2 — they are cheap (text + nightly wheel) and are the only way to notice the failure went away. It only avoids the expensive Stage 3-5 rebuild+fix when both the observable failure and the analyzed code are unchanged.

Stage 1 — Triage (issue-triage)

Call issue-triage on the issue body + comments. It emits issue_type (single-bug / batch-bug / nonbug), batch_kind (skip-list / heterogeneous / null), reproduction_missing (yes / no), scope, runtime_dependencies, and a preliminary handling (agent-fixable / needs-human).

Branch on issue_type (triage already made the batch-vs-nonbug call; no re-detection here):

  • nonbug → stop the fix pipeline; skip to Stage 6 Report with SKIPPED(reason=nonbug).
  • single-bug with reproduction_missing=yes → stop; Stage 6 Report with NEEDS_HUMAN(reason=reproduction_missing). issue-triage's own comment already asks the reporter for a reproducer.
  • single-bug with reproduction_missing=no → continue to Stage 2.
  • batch-bug → Stage 1u, passing through batch_kind (the loop uses it only to label a NOT_REPRODUCED sub-item).

Stage 2 — Reproduce (fix-reproduce)

Only for the single-bug path (issue_type=single-bug). Call fix-reproduce with:

  • reproducer_command — extracted by issue-triage from the issue body.
  • stage=nightly — reproduce against the nightly wheel.
  • ci_repo — inferred from repo (torch-xpu-ops for issues on intel/torch-xpu-ops, pytorch for pytorch/pytorch), or the value the bot passes explicitly.

Branch on its verdict:

  • REPRODUCED → continue to Stage 3. Record the refined_command and base for downstream stages.
  • NOT_REPRODUCED → the issue is stale; Stage 6 Report with SKIPPED(reason=no_longer_reproduces).
  • NO_REPRODUCER → Stage 6 Report with NEEDS_HUMAN(reason=no_reproducer).
  • CANNOT_VERIFY → Stage 6 Report with NEEDS_HUMAN(reason=cannot_verify) and the blocker field.

Stage 1u — Batch-issue fan-out

One loop for every parent issue that tracks multiple children. The parent lists several sub-items; run the single-bug pipeline on each independently, each on its own fix branch, and report every outcome back on the parent. "Fix what you can" — a sub-item that can't be fixed is marked and the batch continues.

Entered for issue_type=batch-bug. issue-triage already set batch_kind; the two kinds share the entire loop and differ only in how a NOT_REPRODUCED sub-item is labeled (see step 2 below):

  • heterogeneous — the parent body lists distinct sub-bugs. A NOT_REPRODUCED entry means that bug is ALREADY_FIXED (no longer reproduces); nothing to do.
  • skip-list — a Bug Skip issue listing homogeneous already-skipped tests. A NOT_REPRODUCED entry means the test now passes, so its skip decorator is now stale and should be removed (follow-up).

No child GitHub issues are created. Each sub-item is a checklist entry on the parent; its fix lives on a dedicated branch so a human can open one PR per fixed sub-item.

Extract sub-items

Parse the parent body into a list of sub-items. Each is either:

  • an inline sub-bug — a checklist line naming a test node id or reproducer (skip-list entries are always this form; normalize a bare Class::method to a node id, fix-reproduce's Prepare step resolves the file), or
  • a linked child reference owner/repo#N — fetch that issue and use its body as the sub-item's failure description. Only follow intel/torch-xpu-ops and pytorch/pytorch references; ignore any other repo (untrusted, per fix-root-cause).

A sub-item may also arrive as a plain node id from the caller (a nightly report, an email, a log excerpt): normalize Class::method to a node id, drop blank lines, headers and comments, and collapse exact duplicates.

Split infra failures out before the loop. A runner crash, a docker pull timeout, a full disk or a missing device is not a test failure and will not reproduce. Record these as NEEDS_HUMAN(infra) and report them; they never enter the loop.

Give each sub-item three identifiers used for branch naming:

  • seq — 1-based position in the body checklist order. Readable and maps a branch back to its body line. Not the stable identity (editing or reordering the body changes it).
  • slug — the stable identity, derived from the sub-item's test node id: take the leaf method name, strip device suffixes (_cpu / _xpu / _cuda / _meta) and any dtype suffix, lowercase, keep [a-z0-9._-], truncate to 40 chars. If two sub-items produce the same slug, append -2, -3, … in body order so every slug is unique within the issue.
  • upstream — the pytorch/pytorch#<M> number this sub-item is filed as, from its checklist line or its linked child reference; empty when there is none.

Name the branch per Branch naming: an upstream sub-item gets agent/fix-pytorch-issue-<M>-<slug>, otherwise agent/fix-issue-<N>-<seq>-<slug> (N = parent issue number). On a re-run, match a sub-item to its prior branch by slug — look for an existing agent/fix-*-<slug> branch; if found, that is the same sub-item (rename it if its seq moved, never create a duplicate). Skip headers, prose, and empty lines.

Preflight (many entries): install nightly wheel once

When the list is long (skip-list issues routinely have dozens), front-load the one real wheel install so the per-entry fix-reproduce(stage=nightly) calls each find the env already current and return fast. fix-reproduce always issues pip install --pre --upgrade (it refuses to trust a stale wheel); running it once here does the real work. There is no skip_wheel_install flag — this is purely an ordering optimization:

pip3 install --pre --upgrade torch torchvision torchaudio \
  --index-url https://download.pytorch.org/whl/nightly/xpu
python -c "import torch; print('nightly:', torch.__version__)"

One fix may cover several sub-items

Entries in a batch are often the same bug in the same class. Fixing each one separately runs root-cause and a source build (1-3 h) per entry and produces the same patch several times.

Do not guess which entries share a fix:

  1. Order the REPRODUCED entries so likely relatives sit together (same test class or file, same error). Ordering only; it decides nothing.
  2. Fix the first entry.
  3. Re-run the other entries in that group against the staged fix. The build is already done, so this costs one test run each. fix-verify already produces a before/after table; extend it to the group.
  4. Entries that now pass go in the fix's covers list and are not fixed again. Entries that still fail take their own turn from step 2.

One fix gets one branch and one fix_result.json, naming in covers every entry it also fixed.

Per-sub-item loop

Capture the two base SHAs once, then for each sub-item reset both checkouts per the shared reset-between-entries recipe — a prior sub-item's staged diff must not bleed into the next.

For each sub-item:

  1. Reset both checkouts to the base SHAs (shared recipe).
  2. Reproduce (fix-reproduce). It detaches the pytorch tree to its base (origin/main or the ci_commit fallback) and returns that base plus a refined_command.

    stage follows the issue, like allow_skip:

    • CI break (pytorch-ci-failure, or a mirrored DISABLED test) → stage=auto. Many CI failures only reproduce on a source build. stage=nightly returns NOT_REPRODUCED as soon as the nightly wheel passes and does not fall through, which records unfixed work as already fixed.
    • Everything else → stage=nightly.

    Branch on the verdict:

    • REPRODUCED → continue to step 3; keep its base and refined_command.
    • NOT_REPRODUCED → nothing to fix; record it and go to the next sub-item. The label depends on batch_kind:
      • heterogeneous → ALREADY_FIXED: the reported bug no longer reproduces on latest nightly (upstream fixed it, or it was flaky). No action needed.
      • skip-list → STALE_SKIP: the test now passes, so its skip decorator is obsolete. Record a follow-up to remove the decorator — the orchestrator does not delete it here (see "Stale skips" below).
    • NO_REPRODUCER → INVALID_ENTRY (renamed/removed test, or malformed). Record, continue.
    • CANNOT_VERIFY → UNVERIFIED (environmental). Record, continue.
  3. Root-cause, then branch. Run Stage 3 (fix-root-cause) first — it returns target_repo, which decides which checkout the fix (and thus the branch) lives in:

    • target_repo=pytorch → target_repo_dir = pytorch_dir, base is the reproduce base on the pytorch tree.
    • target_repo=torch-xpu-ops → target_repo_dir = pytorch_dir/third_party/torch-xpu-ops, base is xpu_ops_base (the submodule's pinned commit from the shared recipe). fix-implement / fix-verify handle the xpu.txt pin rewrite internally; the orchestrator only creates the branch.

    Create the isolated fix branch on target_repo_dir off its base so the diff is pushable on its own (Branch naming):

    # upstream sub-item: branch="agent/fix-pytorch-issue-${M}-${slug}"
    branch="agent/fix-issue-${N}-${seq}-${slug}"
    git -C "$target_repo_dir" checkout -B "$branch" "$base"

    Then run Stage 4 → 5 (same contract and 3-attempt bound as the single-bug path). fix-implement expects exactly this: a fresh branch the orchestrator just created, clean worktree.

  4. On any leaf NEEDS_HUMAN / CANNOT_VERIFY / FAILED (or attempts exhausted): mark this sub-item blocked with the reason, record it, and continue to the next sub-item — never abort the whole batch on one hard sub-item.
  5. On fix-verify PASSED: do NOT push or open a PR. Update the sub-item's fix_result-${slug}.json in place (written at verdict=PENDING_VERIFY when the branch was committed) to verdict=PASSED with fix_repo_dir, branch, base_sha (the sub-item's base from step 3), changed_files, so the workflow can export base_sha..branch per sub-item. As in the single-bug path, commit the branch and write the PENDING_VERIFY record as soon as fix-implement returns READY, not only on PASSED, so a crash mid-sub-item leaves a recoverable record instead of an orphan branch.

Stale skips (batch_kind=skip-list only)

A STALE_SKIP sub-item's skip decorator can be removed, but this orchestrator does not delete it. Deleting a skip is itself a code change that needs its own verify + PR; folding it into this sweep would mix "the skip is obsolete" with "and here's the removal diff" and obscure the batch outcome. Instead, surface every STALE_SKIP in the report as an explicit follow-up (candidate for a human, or a separately invoked fix-implement run to remove the decorator). The report is the hand-off; nothing is auto-removed.

Fan-out report

Post one summary comment on the parent (or surface to the user in interactive mode), and mirror it into the parent's checklist:

<!-- agent:batch-fanout -->

## Batch fan-out results

Base: <torch nightly version or base sha>

| Sub-item | Outcome | Branch / Reason |
|---|---|---|
| test_bar_xpu_float32 | FIXED | agent/fix-issue-4321-1-test_bar |
| test_baz | NEEDS_HUMAN | needs a oneDNN change (`dependency component: oneDNN`) |
| test_qux | NEEDS_HUMAN | attempts_exhausted |
| test_new | ALREADY_FIXED | no longer reproduces on latest nightly |
| test_old | STALE_SKIP | follow-up: remove skip decorator |
| test_dup | COVERED | by test_bar's fix, agent/fix-issue-4321-1-test_bar |
| test_hard | SKIPPED | skip added, tracking issue intel/torch-xpu-ops#1234 |
| test_gone | INVALID_ENTRY | does not collect |

- **FIXED:** N sub-items — one branch each, ready for a human to open
  a PR.
- **NEEDS_HUMAN:** M sub-items — see per-sub-item reason.
- **ALREADY_FIXED:** J sub-items (heterogeneous only) — no longer
  reproduce; the reported bug is resolved, no action needed.
- **STALE_SKIP:** K sub-items (skip-list only) — the test now passes;
  follow up to remove the obsolete skip decorator.
- **INVALID_ENTRY / UNVERIFIED:** P sub-items — malformed/renamed, or
  environmental during reproduce.

*Automated by issue-handler.*

Omit category rows that have no members (a heterogeneous batch has ALREADY_FIXED but never STALE_SKIP, and vice versa). The <!-- agent:batch-fanout --> marker lets a re-run locate and update this same comment in place. On a re-run (Stage 0), only re-process sub-items that are not already FIXED on a live branch, unless human feedback (Stage 0.2) reopens a specific one.

After the loop, go to Stage 6 Report with the aggregate outcome (IMPLEMENTING(fix_verified) if any sub-item was fixed; NEEDS_HUMAN only if every actionable sub-item needed a human — STALE_SKIP follow-ups do not by themselves force NEEDS_HUMAN).

Machine-readable outputs

Written on both paths: the single-bug Stage 4/5 writes fix_result.json, and a batch fan-out writes batch_summary.json plus one fix_result-<slug>.json per fixed sub-item. The <!-- agent:batch-fanout --> comment is for humans; these files let the invoking bot workflow export patches and drive re-verification without re-parsing a comment.

They live under $AGENT_SPACE (the gitignored scratch dir). In pipeline mode the bot workflow always sets it. In interactive mode it is usually unset, which would resolve to /fix_result.json — so fall back to <repo>/agent_space_xpu/ (the same gitignored dir AGENTS.md defines) and report the path you used:

agent_space="${AGENT_SPACE:-$(git -C "$target_repo_dir" rev-parse --show-toplevel)/agent_space_xpu}"
mkdir -p "$agent_space"
  • batch_summary.json — one file listing every sub-item:

    {
      "issue": 4321,
      "kind": "batch-bug",
      "batch_kind": "heterogeneous",
      "sub_items": [
        { "seq": 1, "slug": "test_bar", "outcome": "FIXED",
          "branch": "agent/fix-issue-4321-1-test_bar",
          "target_repo": "torch-xpu-ops",
          "fix_result": "fix_result-test_bar.json",
          "companion": "fix_result-test_bar-pytorch.json",
          "summary": "one-line what/why" },
        { "seq": 2, "slug": "test_baz", "outcome": "NEEDS_HUMAN",
          "branch": null, "reason": "other",
          "reason_detail": "needs a oneDNN change (dependency component: oneDNN)" },
        { "seq": 3, "slug": "test_new", "outcome": "ALREADY_FIXED",
          "branch": null, "reason": "no longer reproduces on latest nightly" }
      ]
    }
  • fix_result-<slug>.json — for each FIXED sub-item, the same schema the single-bug Stage 5 writes as fix_result.json. It MUST include the keys the workflow's schema gate and patch-export read:
    • verdict — PASSED / FAILED / CANNOT_VERIFY / PENDING_VERIFY. Written incrementally: Stage 4 writes PENDING_VERIFY when it commits the branch, Stage 5 updates it to the terminal verdict. Only PASSED is exported as a normal patch; a non-PASSED verdict that still names a reachable branch + base_sha is salvaged as an unverified/ patch so the committed work is not lost with the runner. Keep branch and base_sha accurate even on a non-PASSED record.
    • target_repo — torch-xpu-ops | pytorch,
    • fix_repo_dir — absolute path of the git repo holding the fix commit,
    • branch — per Branch naming,
    • base_sha — the commit the fix branch was started from,
    • changed_files — list of the files the fix touched,
    • depends_on — the fix_result file that must land BEFORE this one, or absent. It is the only record of the order (see below). plus needs_build, refined_command, notes. Suffixed by slug so each sub-item can be exported / re-verified independently.

A fix spanning both repos (fix-root-cause returned a companion_repo) is two units:

  • one branch per repo, named after the issue as usual,
  • one fix_result per repo, the companion's being the primary's name plus -<target_repo> (fix_result-grid_sample-pytorch.json),
  • depends_on on the companion, naming the primary's file,
  • one verdict for both, and notes saying the pair was verified together,
  • batch_summary's sub-item points at the unit that fixes the test.

The patch-export step iterates every fix_result*.json and emits one patch series per unit from base_sha..branch (the branches are not pushed); batch_summary.json enriches it (target_repo, summary line). A unit that reports a verified fix but yields no patch fails the step.

Stage 3 — Root cause (fix-root-cause)

Called on the single-bug path, and once per sub-item from Stage 1u. Call fix-root-cause with the failure description and the refined_command from Stage 2.

Branch on its verdict:

  • IMPLEMENTING(reason=ok) → record target_repo, domain, analyzed_sha, root_cause, fix_strategy, then create the fix branch before Stage 4 — target_repo is what decides which checkout it lives in, and fix-implement expects a fresh branch with a clean worktree ($base is Stage 2's reproduce base; the batch path already did this in its own step 3, with its own branch name):

    # upstream-filed bug: branch="agent/fix-pytorch-issue-${M}"
    branch="agent/fix-issue-${N}"
    git -C "$target_repo_dir" checkout -B "$branch" "$base"
  • NEEDS_HUMAN → Stage 6 Report, carrying the leaf's one-line reason_detail as the justification — that is what a maintainer reads. Every reason lands on the same terminal status (NEEDS_HUMAN / agent:needs-human), so do not shop for a code: the two worth branching on are no_registered_domain (do not retry, see Retry policy) and invalid_reproduction (re-run Stage 2 first).

Stage 4 — Implement (fix-implement)

Call fix-implement with triage_result, pytorch_dir, target_repo_dir (derived from target_repo), and allow_skip:

allow_skip follows the issue:

  • CI break — the issue carries the pytorch-ci-failure label, or mirrors a DISABLED test from pytorch/pytorch. allow_skip=true. Fix it in place where you can, in pytorch or here. Skip only when the fix needs a dependency, information you do not have, or feature-sized work; fix-implement then files a tracking issue for the real fix.
  • Everything else — allow_skip=false. Never add a skip decorator.

If the caller states the flag, the caller wins.

Branch on the verdict:

  • READY(reason=ok) → commit the staged fix onto the branch created in Stage 3, and write the incremental fix_result.json, then continue to Stage 5. Do NOT wait for Stage 5 to persist — if verify then crashes or the context runs out, the committed branch would otherwise be an orphan the Export step flags as a lost fix. Committing here keeps the branch and the hand-off record in lock-step:

    git -C "$target_repo_dir" commit -m "fix: <one-line summary> (#${N})"

    Then write fix_result.json (see "Machine-readable outputs" for the path and its interactive-mode fallback) with the fields known so far and verdict=PENDING_VERIFY: target_repo, fix_repo_dir, branch, base_sha, changed_files, analyzed_sha, root_cause, notes. The branch is local and dies with the runner, so this record is what lets the workflow salvage the commit as an unverified/ patch if verification never finishes — without it, a crash after this point loses the work outright.

    With a companion_repo, do this twice, target_repo first: call fix-implement again with target_repo_dir set to the companion checkout, branch off that repo's HEAD, commit, and write its record per "Machine-readable outputs".

  • NEEDS_HUMAN → Stage 6 Report, carrying the leaf's reason_detail as the justification. The label is agent:needs-human whatever the reason (skip_outside_target_repo / skip_guard_rejected / no_fix_possible / etc.).

Stage 5 — Verify (fix-verify)

Call fix-verify with refined_command (from Stage 2), target_repo_dir, and changed_files (from Stage 4). fix-verify unconditionally produces the FAIL->PASS before/after table and runs spin fixlint on a passing result — no flags to pass.

A two-repo fix is verified once, as a pair — one tree, one build. Both records take that verdict.

Branch on the verdict:

  • PASSED(reason=ok) → the fix is verified. The branch and fix_result.json already exist from Stage 4; update the record in place — set verdict=PASSED and add needs_build, the before/after result, and any final notes — then go to Stage 6 with IMPLEMENTING(fix_verified). fix-verify leaves its spin fixlint fixes staged: amend them into the Stage 4 commit (git commit --amend --no-edit) so they reach the exported patch — never a second commit. The workflow exports base_sha..branch as the patch. Never push or open a PR.
  • FAILED → loop back to Stage 4 with the failure output as additional context (see "Iterative loop bounds"). On a retry the branch already carries the previous attempt's commit, so amend it (git commit --amend --no-edit after staging the new edits) rather than creating a second commit or a new branch — base_sha..branch must stay a single reviewable fix, and Stage 5's clean-worktree assertion requires nothing left uncommitted. If attempts are exhausted, update fix_result.json to verdict=FAILED with the last failure in notes so the record explains the branch, then Stage 6 Report NEEDS_HUMAN(reason=attempts_exhausted).
  • CANNOT_VERIFY → update fix_result.json to verdict=CANNOT_VERIFY with the blocker in notes, then Stage 6 Report NEEDS_HUMAN(reason=<verify's reason>). Do not loop on CANNOT_VERIFY — the environment problem will not fix itself.

Stage 6 — Report

At the end, summarize the outcome. In interactive mode present this to the user in plain language. In pipeline mode advance the issue's agent:status to the terminal stage (DONE / NEEDS_HUMAN / SKIPPED) and update the checklist per execution-modes.md; a batch fan-out run also posts the <!-- agent:batch-fanout --> summary.

Always include in the summary:

  • Issue: link/number and one-line title.
  • Path: single-bug / batch (with batch_kind).
  • Outcome: IMPLEMENTING(fix_verified) / NEEDS_HUMAN(<reason>) / SKIPPED(<reason>).
  • Root cause (from Stage 3, if reached).
  • Files changed (from Stage 4, if reached).
  • Verification (from Stage 5, if reached).
  • For a batch: per-sub-item outcome + branch name (FIXED) or reason (NEEDS_HUMAN / ALREADY_FIXED / INVALID_ENTRY / UNVERIFIED), plus any STALE_SKIP follow-ups when batch_kind=skip-list.

If the outcome is IMPLEMENTING(fix_verified), the fix is committed on its fix branch (Branch naming) and recorded in fix_result.json. The invoking workflow reads that, exports base_sha..branch as a patch artifact, and a human applies it. Do not push or open the PR from this skill.

batch-fanout and summary block templates

Both are this skill's own blocks, so write them collapsed as shown — there is no ## heading to strip, unlike the leaf blocks this skill wraps.

Batch runs only:

<!-- agent:batch-fanout -->

<details>
<summary><b>Batch fan-out results</b></summary>

Base: `torch-xpu-ops@<short_sha>`, pytorch `origin/main@<short_sha>`
(reproduce base: nightly `<wheel version>`)

| Sub-item | Outcome | Branch / Reason |
|---|---|---|
| 4. `test_foo_xpu_float8_e4m3fn` | FIXED | `agent/fix-pytorch-issue-197334-test_foo` |
| 5. `test_foo_xpu_float8_e5m2` | COVERED | by sub-item 4's fix, same branch |

- **FIXED:** <n> sub-items — <what a human should do with the branches>
- **COVERED:** <n> sub-items — <why no separate branch>
- **NEEDS_HUMAN / STALE_SKIP / INVALID_ENTRY / UNVERIFIED:** none.

*Automated by issue-handler.*

</details>

Every run. The sign-off is outside </details> — it is the comment's footer, not part of the summary:

<!-- agent:summary -->

<details>
<summary><b>Summary</b></summary>

<2-4 sentences: the one bug behind the sub-items, the fix in one clause,
and what verification showed. No bullets.>

- **Branch:** `agent/fix-issue-<N>[-<seq>-<slug>]`, or
  `agent/fix-pytorch-issue-<M>[-<slug>]` for an upstream-filed bug
- **Base:** `<full 40-char sha>`
- **Changed files:** `path/to/file.cpp`
- **Nothing was pushed.** The patch is exported as a workflow artifact for
  a human to review and apply.

</details>

_Generated by [fix job](<run url>)._

A two-repo fix needs the order spelled out, or whoever opens the PRs gets it wrong and breaks CI. At the top of the Summary body:

> [!IMPORTANT]
> **Two PRs, in this order.**
> 1. `<lands-first repo>` — patch dir `<branch>-<repo>`.
> 2. `<companion repo>` — patch dir `<branch>-<repo>`, after <what it waits
>    on, e.g. the `xpu.txt` pin bump carrying step 1>.
>
> **No closing keyword in either PR** (`Fixes` / `Closes` / `Resolves`):
> merging one would close the issue with the other half still out. Reference
> the issue plainly; a human closes it once both have landed.

and one line outside </details>, above the _Generated by_ footer, since that block renders collapsed:

**Two patches, and the order matters — see Summary.**

Review request block

When the job ends, the fix workflow appends the <!-- agent:review --> block itself — the verdict menu a reviewer copies into a new comment. Do not write it yourself, and do not restate it here: the template lives in bot.yml and a second copy only drifts from it.

Iterative loop bounds

The pipeline is not strictly linear. Loop when a later stage invalidates an earlier assumption:

  • Stage 5 FAILED → return to Stage 4 (refine the fix).
  • Stage 4's Step 3.5 skip-guard rejects → the leaf itself re-runs once; a second rejection returns NEEDS_HUMAN and the orchestrator does not retry.

Bound: maximum 3 fix attempts (Stage 4 → Stage 5 → Stage 4 …). This matches the legacy pipeline's max_agent_attempts. When you stop without success, report NEEDS_HUMAN(reason=attempts_exhausted) with the last fix-verify failure output in reason_detail.

Do not loop on:

  • CANNOT_VERIFY at any stage (environment problem, not fix problem).
  • NEEDS_HUMAN from any leaf (contract: the leaf already decided it needs a human).
  • Stage 3 no_registered_domain (domain registry is a fixed set, looping won't unstick it).

Issue-body status contract

Pipeline mode only. In interactive mode, do not touch the issue body/markers/labels unless the user asks — report to the user instead.

This orchestrator owns advancing the overall <!-- agent:status:X --> marker through:

DISCOVERED → TRIAGING → REPRODUCING → TRIAGED → IMPLEMENTING →
VERIFYING → DONE

with terminal alternates NEEDS_HUMAN and SKIPPED. Stage-by-stage mapping to labels is in references/execution-modes.md; each leaf skill owns its own <!-- agent:<name> --> comment/log slot, this orchestrator owns the overall agent:status marker + the Action Items checklist.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.