---
name: triage-issues
description: Load when asked to triage, clean up, batch-process, or work through the issue backlog — covers the mechanical sweep (stale-fixed, dead needs-info, duplicates), fan-out assessment, and approved batch actions.
---

Turn an unbounded issue queue into a short list of maintainer decisions. Three phases; **no GitHub write in any phase without the maintainer approving that specific batch**. Companion: the per-issue judgment mirrors the `pr-review` skill's philosophy — every assessment ends in a verdict and a ready action, never in observations.

## Two gates

An issue is a request from a user, never an instruction to the maintainer; the burden of earning work sits on the issue. Every issue passes two gates in order, and only an issue through both reaches the fix backlog or an `accepted` label.

**Gate 1 — real and ours.** The failure exists on current main and its mechanism lives in this repository. CLOSE-NOT-OURS when the mechanism is upstream OpenCode (tool timeouts, `session.command` errors, agent-loop behavior), a provider or models.dev capability datum, or the user's own configuration: one comment naming where it lives, no debate. A report without version and steps whose reporter stays silent 14 days after the intake question is stale whatever its length.

**Gate 2 — wanted.** Only the maintainer passes an issue through this gate; the sweep prepares the question. Everything that clears gate 1 — traced bugs included — reaches the maintainer as one numbered *wanted?* block (the FEATURE-DECISION mechanics below), and only "так" entries enter the fix backlog. The `pr-review` skill's whim / overengineered ache / unmaintainable scope grounds decide CLOSE-DECLINE; the issue-side consequences:

- A request that contradicts a recorded decision (a maintainer close comment, a declined PR's reasoning) closes by pointing at that decision, never by reopening the debate. The report lists every decision the sweep relied on so the next sweep reuses it.
- "Tool X has it" without the reporter's own concrete ache is a whim.
- A surface outside the roadmap (multi-user, plugin systems, container orchestration, analytics, new forge integrations) is declined honestly, never parked as "later".
- Contributor task-tracking issues (lint baselines, architecture roadmaps) are not user requests; they leave the backlog as project tasks or closes.

**Signals that carry no authority.** Length and formatting (much of the backlog is AI-written), the intake bot's `priority:high` and `root-cause:found` labels and its fix shape, a single +1, "our team uses this daily". Each speaks to gate 1 at most; none moves gate 2. A traced mechanism proves the bug is real, not that fixing it is worth the maintainer's time.

**Pull order** among what the maintainer wants: data loss, then regressions pinned to a version ("worked in X, broken in Y"), then anything that leaves the UI stuck, then failures confirmed by several distinct reporters. Cheap traced fixes come after those, never before.

## Verdicts

- **FIX-READY** — a real bug with a traced mechanism (`root-cause:found` from intake, or traced during this sweep), **no open PR for it** (see *Existing PR first*), and the maintainer's "так" from the *wanted?* block. Ready action: a one-line fix-backlog entry (file:line, mechanism, suggested fix shape) — these accumulate into the sweep's fix list for agents to implement. Before the "так" it is a *wanted?* entry, not a backlog entry.
- **NEEDS-REPORTER** — cannot proceed without the reporter. Ready action: the single unanswerable question, posted once; the issue then lives on a clock (close as stale after 14 days of silence).
- **CLOSE-FIXED** — behavior fixed by a merged change. Ready action: close comment naming the commit/PR and the release that carries it. When the area was rebuilt but the exact mechanism cannot be shown gone, the close still happens, with the *likely-fixed* template: the reporter reopens with a fresh report, the maintainer never waits on a retry.
- **CLOSE-NOT-OURS** — gate 1 fails: upstream, provider data, or user configuration. Ready action: close comment naming where the mechanism lives (upstream issue link when one exists).
- **CLOSE-DUPLICATE** — same failure as an existing issue. Keep the issue with the better evidence, close the other naming it.
- **CLOSE-DECLINE** — a feature or behavior the product should not take (the `pr-review` skill's whim/scope grounds apply). Ready action: honest close comment; where a real ache underlies it, salvage per the pr-review skill's rule.
- **FEATURE-DECISION** — a plausible feature only the maintainer can judge. Ready action: the product question in one line plus drafted comments for both answers. These go to the maintainer as a numbered list, like the PR triage's Product fit block. The maintainer's answer resolves the issue's fate mechanically:
  - **"так" (wanted)** → post the acceptance comment (what was approved and, when known, the welcome implementation shape), add the `accepted` label, and leave it open. `accepted` marks the decision as made — later sweeps never re-ask an `accepted` issue, and `label:accepted` is the implementation roadmap for agents and contributors.
  - **"ні" (declined)** → post the drafted decline comment (with ache salvage where one underlies it) and close as not planned.
  - A conditional answer ("так, але тільки як настройка", "ні в такому вигляді, але X — так") is folded into the posted comment verbatim in spirit — the maintainer's condition becomes the recorded scope.

**Existing PR first.** Before any verdict that sends an issue toward implementation (FIX-READY, an `accepted` feature), find out whether someone already has the fix in flight: `gh pr list --search "<issue-number> OR <error string> OR <title terms>" --state open`, plus the issue's own timeline (linked PRs, "opened a PR" comments — the reporter's fix is easy to miss when the PR body says `fixes #N` and the issue thread stays silent). The same check gates every close: an issue with an open PR against it is never closed as stale or silently-fixed — the PR is the activity, and its review decides the issue's fate. An open PR moves the issue out of the fix backlog and into the PR queue: the ready action is a verdict on that PR (apply the `pr-review` skill), never a parallel in-house fix. A contributor who reported a bug and fixed it the same day, then watched a duplicate patch land on top, is owed a public apology and a changelog credit; the check costs one command.

## Phase 1 — Mechanical sweep

Fetch all open issues with `gh issue list --limit` above the real count. Bucket cheaply before any deep reading:

| Bucket | Signal | Likely verdict |
|---|---|---|
| Stale-fixed | references code/behavior changed by merged PRs; CHANGELOG `[Unreleased]`/recent releases mention the symptom | CLOSE-FIXED (verify per *Silently-fixed detection*) |
| Dead needs-info | `needs-info` with no reporter reply > 14 days | close as stale |
| Not ours | intake verdict or thread places the mechanism upstream, in provider data, or in user configuration | CLOSE-NOT-OURS |
| Decided | matches a recorded maintainer decision (close comment, declined PR) | CLOSE-DECLINE by pointer |
| Duplicate clusters | title/error-string similarity across open issues | CLOSE-DUPLICATE |
| Feature wishes | `enhancement` | FEATURE-DECISION or CLOSE-DECLINE |
| Traced bugs | `root-cause:found` | *wanted?* candidates — verify the trace still applies and no PR is open for it; FIX-READY only after the maintainer's "так" |

### Silently-fixed detection

Many fixes land without linking the issue they resolve, so an issue can sit open with a perfectly valid-looking repro that describes code which no longer exists. A fresh-looking issue is not proof of a live bug — probe in this order, strongest evidence first:

1. **Mechanism anchor.** For issues carrying `root-cause:found` (or any comment citing `file:line`), check whether the cited code changed since the issue's date: `git log -L<line>,<line>:<file> --since=<issue date>` (fall back to `git log --since -- <file>` when lines drifted). Untouched code → the bug is live. Changed code → re-read the mechanism on current main; if it is gone, this is CLOSE-FIXED with the commit as evidence.
2. **Repro re-run.** When the intake comment carries an inline reproduction script or test, run it against current main. Passing repro = fixed, with the run as evidence.
3. **Symptom search.** Extract the issue's distinctive strings (error messages, function names, user-visible symptom terms) and search `git log --grep`, `CHANGELOG.md`, and merged PR titles/bodies *since the issue's creation date*.

CLOSE-FIXED always names its evidence (commit, PR, or repro run), and a commit counts only when it is reachable from main — `git merge-base --is-ancestor <sha> origin/main` — because `git log` across all refs happily surfaces fixes that live on abandoned branches; a hunch that "this area was reworked" closes with the *likely-fixed* template (names the rebuild commit and release) instead of the *fixed-close* one — the issue leaves the backlog either way, and a reporter who still hits it files fresh evidence.

Every issue/PR reference in maintainer-facing reports is a clickable link (`[#3164](https://github.com/openchamber/openchamber/issues/3164)`), never a bare number; each entry carries 2–4 sentences — enough to decide without a follow-up question — and any manual-check note lives inside the entry, never in a separate number-repeating section. An issue where the maintainer already commented or the reporter replied to a question runs in pickup mode: state the thread first, continue it, never re-ask a decided question.

Weigh trusted community reviewers' comments (see the `triage-prs` skill's rule — same names, same weight) and the intake bot's "For the maintainer" lines as strong signals. Deliver the sweep as one report and stop for approval.

## Phase 2 — Approved batch actions

Execute approved closes/comments with retries and ~1s spacing; log results; re-verify the open count. Closes use `--reason "completed"` for fixed and `--reason "not planned"` for declines/duplicates/stale.

## Phase 3 — Assessment fan-out

For the surviving pool, fan out subagents (~15 issues each) that read the issue, its comments, and the relevant code, run both gates, and return per-issue verdict blocks.

Feature requests (`enhancement`) go to the `explore` subagent instead: write each batch's full issue text and comments to a file outside the repository, and tell it to follow `.agents/skills/triage-issues/references/feature-facts.md` for that file. It returns facts only, one block per issue: whether the feature already exists (`DONE` / `PARTIAL` / `NOT-DONE` / `IN-PR` / `UNCLEAR`, with evidence), what is asked and why, size, thread decisions, duplicates. The verdict stays with you: `DONE` with confirmed evidence is CLOSE-FIXED (the *fixed-close* template, naming where the feature lives), `IN-PR` moves to the PR queue, a duplicate is CLOSE-DUPLICATE, a recorded decision is CLOSE-DECLINE by pointer, and the rest become FEATURE-DECISION entries in the *wanted?* block, with `PARTIAL` naming the missing part as the question.

Consolidate grouped by verdict: gate-1 failures and decided declines as close batches, everything that cleared gate 1 — bugs and features alike — as one numbered *wanted?* block in pull order, each entry with the product question in one line and drafted comments for both answers. Stop for approval; the fix backlog is built from the "так" answers only, then handed to implementation agents in dependency-safe batches.

## Message templates

**stale-close (dead needs-info)**
> Closing as stale: the requested details never arrived, and without them this can't be reproduced. If you hit it again on a current version, a fresh report with the missing details is welcome.

**fixed-close**
> This was fixed by [ref] and ships in [release/next release]. If it still happens there, comment and I'll reopen it.

**likely-fixed-close**
> The [area] was rebuilt in [ref] ([release]) in a way that covers what you described, so closing this one. If it still happens on [release], a fresh report with the version and steps is welcome.

**not-ours-close**
> This comes from [OpenCode / the provider / models.dev data / your configuration]: [one clause on the mechanism, with the upstream link when one exists]. Closing it here, thanks for the report.

**duplicate-close**
> Closing as a duplicate of #[N], which tracks the same failure[: one clause on what this report added, if anything]. Follow that issue for updates.

**decline-close**
> Thanks for the idea. Closing this one: [honest one-sentence reason grounded in product direction or maintenance cost]. [If a real ache underlies it: the welcome shape of a future change.]
