# Issue Handler

> 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.

- Skill: `intel/issue-handler` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add intel/issue-handler`
- Raw SKILL.md: https://api.skillmd.com/api/skills/intel/issue-handler/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: intel (https://skillmd.com/u/intel)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/intel/issue-handler

---


# 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`. 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](#pipeline-overview)
- [Inputs](#inputs)
- [Execution modes](#execution-modes)
- Stage 0: [Re-run gate](#stage-0--re-run-gate-first-run-vs-re-run)
- Stage 1: [Triage](#stage-1--triage-issue-triage)
- Stage 2: [Reproduce](#stage-2--reproduce-fix-reproduce)
- [Stage 1u: Batch-issue fan-out](#stage-1u--batch-issue-fan-out) (skip-list + heterogeneous)
- Stage 3: [Root cause](#stage-3--root-cause-fix-root-cause)
- Stage 4: [Implement](#stage-4--implement-fix-implement)
- Stage 5: [Verify](#stage-5--verify-fix-verify)
- Stage 6: [Report](#stage-6--report)
- [Iterative loop bounds](#iterative-loop-bounds)
- [Issue-body status contract](#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 agent/fix-issue-<N>-<seq>-<slug>
                               → 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.

| Stage | Leaf skill | Purpose |
|-------|-----------|---------|
| 1. Triage | `issue-triage` | Text-only classification: single-bug / batch-bug (+ `batch_kind`) / nonbug, `scope`, `runtime_dependencies`, preliminary verdict |
| 2. Reproduce | `fix-reproduce` | Verify the failure still reproduces against the nightly wheel (`stage=nightly`) |
| 3. Root cause | `fix-root-cause` | Deep source analysis, `target_repo`, `domain`, `IMPLEMENTING`/`NEEDS_HUMAN` |
| 4. Implement | `fix-implement` | Edit code, stage the diff (never commit) |
| 5. Verify | `fix-verify` | Run the refined command against source build, PASSED/FAILED/CANNOT_VERIFY |
| 6. Report | this skill | Summarize 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](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.

## 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:

```bash
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.)

```bash
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 two 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.

Branch name is `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-issue-<N>-*-<slug>` (any seq); if found,
that is the same sub-item (rename to the current seq if it 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:

```bash
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](references/execution-modes.md#reset-between-entries-recipe-batched-fan-out)
— 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:

   ```bash
   git -C "$target_repo_dir" checkout -B "agent/fix-issue-${N}-${seq}-${slug}" "$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 | cross_repo_coordinated |
| 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:

```bash
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:

  ```json
  {
    "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",
        "summary": "one-line what/why" },
      { "seq": 2, "slug": "test_baz", "outcome": "NEEDS_HUMAN",
        "branch": null, "reason": "cross_repo_coordinated" },
      { "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` — `agent/fix-issue-<N>-<seq>-<slug>` (single bug: `agent/fix-issue-<N>`),
  - `base_sha` — the commit the fix branch was started from,
  - `changed_files` — list of the files the fix touched,
  plus `needs_build`, `refined_command`, `notes`. Suffixed by slug so each
  sub-item can be exported / re-verified independently.

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):

  ```bash
  git -C "$target_repo_dir" checkout -B "agent/fix-issue-${N}" "$base"
  ```
- `NEEDS_HUMAN` → Stage 6 Report with the specific
  `reason` (`task_or_feature` / `feature_gap` / `hardware_specific` /
  `cross_repo_coordinated` / `no_registered_domain` / etc.). Each
  reason maps to a different final `agent:status` value; see
  [execution-modes.md](references/execution-modes.md).

## 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:

  ```bash
  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.
- `NEEDS_HUMAN` → Stage 6 Report. The specific `reason`
  (`skip_outside_target_repo` / `skip_guard_rejected` /
  `no_fix_possible` / etc.) drives the final label.

## 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.

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](references/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 `agent/fix-issue-<N>` branch 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.**

### Review request block

When the job ends, the `fix` workflow appends a template to the session
comment for a human to review the fix. Do not write it yourself:

```
**strong-accepted** <!-- review: strong-accepted -->
**weak-accepted** <!-- review: weak-accepted -->
**weak-rejected** <!-- review: weak-rejected -->
**strong-rejected** <!-- review: strong-rejected -->

notes:
```

A reviewer copies it into a new comment, keeps one line, and says why.

## 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](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.

