Skip to content

chore(repo): add the issue-triage tooling - #36883

Draft
AgentEnder wants to merge 11 commits into
masterfrom
chore/issue-triage-tooling
Draft

chore(repo): add the issue-triage tooling#36883
AgentEnder wants to merge 11 commits into
masterfrom
chore/issue-triage-tooling

Conversation

@AgentEnder

Copy link
Copy Markdown
Member

Current Behavior

Triaging issues in this repo is unscripted, and the one step that actually counts is easy to miss:
scripts/issues-scraper/scrape-issues.ts counts any open issue with no scope: label as untriaged, and
.github/workflows/issue-notifier.yml posts that number to Slack every Sunday.

There is also no way to look over a batch of proposed label, assignee and comment changes before they hit
GitHub — you either run gh issue edit and live with it, or you don't triage.

Expected Behavior

Adds the issue-triage tooling under .claude/. Nothing reaches GitHub until a human approves it.

  • .claude/tools/triage — stages every mutation as a reviewable record under .nx-issue-triage/
    rather than calling gh directly: stage, list, show, review, feedback, apply, plus
    apply --dry-run to print the exact gh commands without running them. Records are plain markdown and
    hand-editable. Every write is journalled to ~/.nx-triage/journal.jsonl, outside the state directory,
    so recover can restore records if that directory is cleared.
  • .claude/tools/triage-tui — the approval TUI (pnpm triage-tui): approve, reject, request changes
    with a note back to the agent, open the issue on GitHub, or edit the record in $EDITOR.
  • .claude/tools/triage-owners.json — area routing. Weighted round-robin over per-area candidates
    plus freeform guidance, so a sweep spreads across the team instead of clustering on one person, and a
    routing decision someone had to reason out gets recorded rather than re-derived next time.
  • .claude/skills/triage-issues/ — the workflow itself, with label and reply references.

The guardrails are enforced by the tool rather than only documented. It refuses to stage a record that
sets the bot-owned stale or outdated labels, that starts two stale countdowns at once, that adds and
removes the same label, or that closes an issue without a comment.

Opening as a draft: this is the initial version, and further refinements to the tool, the TUI and the
skill are still local.

Related Issue(s)

None — repo tooling, no linked issue.

@netlify

netlify Bot commented Sep 1, 2026

Copy link
Copy Markdown

Deploy Preview for nx-docs ready!

Name Link
🔨 Latest commit 7ed7417
🔍 Latest deploy log https://app.netlify.com/projects/nx-docs/deploys/6a9791965a15470008aee02a
😎 Deploy Preview https://deploy-preview-36883--nx-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify

netlify Bot commented Sep 1, 2026

Copy link
Copy Markdown

Deploy Preview for nx-dev ready!

Name Link
🔨 Latest commit 7ed7417
🔍 Latest deploy log https://app.netlify.com/projects/nx-dev/deploys/6a9791964563670008272928
😎 Deploy Preview https://deploy-preview-36883--nx-dev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@nx-cloud

nx-cloud Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

View your CI Pipeline Execution ↗ for commit 7ed7417

Command Status Duration Result
nx affected --targets=lint,oxlint,test,build,e2... ⛔ Cancelled 1h 39m 22s View ↗
nx run-many -t check-imports check-lock-files c... ✅ Succeeded 4s View ↗
nx-cloud record -- pnpm nx-cloud conformance:check ✅ Succeeded 43s View ↗
nx build workspace-plugin ✅ Succeeded <1s View ↗
nx-cloud record -- nx sync:check ✅ Succeeded 16s View ↗
nx-cloud record -- nx format:check ✅ Succeeded 3s View ↗

☁️ Nx Cloud last updated this comment at 2026-09-02 04:45:59 UTC

@socket-security

socket-security Bot commented Sep 1, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednpm/​react@​19.2.81001008497100
Addednpm/​@​opentui/​core@​0.5.9951009398100
Addednpm/​@​opentui/​react@​0.5.91001009998100

View full report

nx-cloud[bot]

This comment was marked as outdated.

AgentEnder and others added 8 commits September 1, 2026 18:42
Triage produces label changes, assignees and public comments. Applying
those straight from an agent means the first time a human sees a mistake,
it is already on a public issue and cannot be unsent. This stages each
mutation instead, and only a human moves it forward.

- `.claude/tools/triage` writes one hand-editable markdown record per
  issue and applies approved ones through gh. It refuses to stage the
  bot-owned `stale`/`outdated` labels, two stale countdowns at once, a
  label both added and removed, or a close with no comment explaining it.
- `.claude/tools/triage-tui` is the approval surface: approve, reject,
  leave a note the agent reads back with `triage feedback`, open the
  record in $EDITOR, or open the issue on GitHub.
- `triage-owners.json` routes scopes to maintainers and rotates rows with
  several owners, so one person does not absorb a whole sweep.

The TUI is its own workspace package because ink depends on yoga-layout,
which uses top-level await and cannot be transformed to CommonJS; the
sibling package.json scopes just that directory to ESM. It also keeps ink
out of the root dependency tree. `pnpm triage-tui` runs it.

Everything except the TUI runs on node builtins alone, so applying still
works in a fresh clone before pnpm install.
A repo-facing workflow for triaging nrwl/nx issues: apply the labels that
make an issue count as triaged, ask for a reproduction when one is
missing, run it when one exists, and propose a priority and an owner.
Every mutation is staged through .claude/tools/triage for a human to
approve; the skill never writes to GitHub itself.

The definition of "triaged" is not a convention here. Any open issue with
no `scope:` label increments untriagedIssueCount in
scripts/issues-scraper/scrape-issues.ts, which issue-notifier.yml posts to
Slack weekly, so the queue query mirrors that scraper exactly.

Things the skill encodes because they are easy to get wrong:

- A `blocked:` label is an eviction timer, not a note. schedule-stale.yml
  gives 7 days to `stale` and 21 more to closed. Removing one when the
  reporter answers is a triage duty, since the bot only strips `stale`.
- `stale` and `outdated` are bot-owned and must never be set by hand.
- closedByPullRequestsReferences finds a PR already in flight, including
  on open issues. Scraping /timeline for cross-referenced events does not.
- The scope table is built from how maintainers actually label, not from
  the label descriptions: `scope: bundlers` reads "webpack, rollup" but
  covers vite, esbuild, rspack and rsbuild too.
- A `Steps to Reproduce` section containing only `1.` is the bug form's
  seeded default, not a reproduction. No labels at all means the issue was
  filed outside the form, so none of its required fields are guaranteed.
- Before proposing `community`, check the fix is contributable. The repo
  does ship graph UI source under graph/, but those packages import the
  published closed-source @nx/graph, so "it's a graph bug" decides nothing.

references/labels.md holds the scope map and close reasons;
references/replies.md holds comment templates.
Co-authored-by: AgentEnder <AgentEnder@users.noreply.github.com>
A reproduction clones a stranger's repo and installs it with that repo's own
package manager. Fetching that into the shared object store would mix an
untrusted history into the repo every PR review reads.

- `sandbox start` without `--checkout` now provisions an empty private
  directory in the shared host instead of a worktree of nx, with `root`
  pointing at the workspace itself since there is no `nx` subtree.
- `sandbox exec` skips `ensureInstalled` for a scratch workspace: there is
  nothing to install, and `mise install && pnpm install` fails in an empty
  directory.
- reproduce-issue is rewritten onto that mode, so repro code runs in the
  shared host rather than a container of its own.
Nothing reaches GitHub until a human approves it. Every label, assignee,
comment and close is written to `.nx-issue-triage/<N>.md` first, and only
`apply` calls `gh`.

- Add the record lifecycle: stage, list, show, set, feedback, apply, rm,
  prune, and `notify` for pushing into a live TUI.
- Refuse to stage a record that sets a bot-owned label, starts two stale
  countdowns at once, adds and removes the same label, or closes an issue
  with no comment, so the guardrails hold when the skill is only skimmed.
- Journal every write to ~/.nx-triage/journal.jsonl, outside the state
  directory, so `recover` can restore records if that directory is cleared.
  It is gitignored and shared between sessions, so it really does get lost.
- Record the issue's current labels and assignees at stage time, so review
  can tell a real mutation from a no-op restatement.
- Move owner routing from triage-owners.json to a documented triage-owners.md.
  The table now carries a roster that `owner` asserts against, so a handle
  that has left the team fails loudly instead of quietly drawing issues, plus
  freeform per-area guidance and per-person standing notes that JSON had
  nowhere to put.
Reviewing staged records by reading markdown and running `triage set` by
hand does not scale past a handful, and a sweep stages dozens.

- Approve, reject, request changes with a note, open the issue on GitHub,
  and edit a record in $EDITOR from the list.
- Reload as the state directory changes, so records land in front of the
  reviewer while the agent is still staging them.
- Accept pushes from `triage notify`: reload, select a record, or show a
  message, so the agent can point the reviewer at what it just restaged.
- Render the record body rather than the raw file, and keep comment text
  selectable without picking up soft-wrap breaks.
The skill's failure mode was a confident record built on reading rather than
running. Every excuse in the new table was written into a real record here,
sounded reasonable, and was wrong.

- Fetch the whole queue in one GraphQL call instead of three REST calls per
  issue, and read each issue out of the file rather than into context.
- Default to attempting a reproduction. `repro: not run` is legitimate in
  exactly two cases, an open PR or nothing runnable, and must name which in
  its first sentence. audit-repro.sh checks that mechanically.
- Add a Step 3 check for a fix that landed WITHOUT a closing keyword:
  #36426 was fixed by a PR that said `Fixes` on its later duplicate, so
  every PR-detection path came back clean on an issue already fixed.
- Require plain prose outside the collapsed Triage notes block on every
  comment. A bare `<details>` reads as a bot dropping an artifact on the
  thread, which is what the attribution split exists to prevent.
- Note that a Windows VM is reachable, so `os: windows` is not unrunnable,
  and that the sandbox is Linux, so a clean run narrows a darwin bug rather
  than clearing it.
The agent and the reviewer work in parallel: records land while the reviewer
is still deciding on earlier ones. That needs the TUI beside the
conversation, not in front of it, and it needs decisions coming back without
the agent polling.

- Open `triage review` in a herdr or tmux split, self-closing on quit.
- Arm watch-triage.sh as the return path: one line per status change with
  the reviewer's note, exiting once every record settles. It also reports if
  the state directory disappears mid-review, which a stray clean elsewhere
  in a shared checkout really can do.
- Say plainly that this needs a multiplexer rather than degrading to a
  foreground TUI that takes the session over, or a detached one that exits
  immediately for want of a terminal.
- Apply approvals as they arrive rather than at the end, so a reviewer who
  approves ten and steps away comes back to ten applied.
@AgentEnder
AgentEnder force-pushed the chore/issue-triage-tooling branch from 30c281d to 4bc1615 Compare September 1, 2026 22:43
nx-cloud[bot]

This comment was marked as outdated.

An arm that quietly exercises the absence of the feature under test comes
back clean, and reads as a negative result.

- `CLAUDECODE=1` makes `isAiAgent()` true, and `shouldUseTui()` returns
  false on it, so an agent-run TUI test is never on the TUI path.
- A pty with no winsize refuses the TUI outright via the `columns > 0`
  check, which is how a hook runner's inner pty behaves.
- Assert the state under test by calling the predicate or finding a
  byte-level tell, rather than inferring it from the command line.

#36579 was measured twice on the non-TUI path before anyone checked.
nx-cloud[bot]

This comment was marked as outdated.

nx-cloud[bot]

This comment was marked as outdated.

@nx-cloud nx-cloud Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

At least one additional CI pipeline execution has run since the conclusion below was written and it may no longer be applicable.

Nx Cloud is proposing a fix for your failed CI:

We ran pnpm nx format to resolve the Prettier formatting check failure on .claude/skills/triage-issues/SKILL.md, a new file introduced by this PR that was committed without being passed through the formatter. This brings the file into compliance with the repo's formatting standards and will allow the nx format:check task to pass.

Tip

We verified this fix by re-running nx-cloud record -- nx format:check.

diff --git a/.claude/skills/triage-issues/SKILL.md b/.claude/skills/triage-issues/SKILL.md
index a412bf6e4..d57c8f5a5 100644
--- a/.claude/skills/triage-issues/SKILL.md
+++ b/.claude/skills/triage-issues/SKILL.md
@@ -381,11 +381,11 @@ problem, and check it against the flags you are about to set.
 **Your own environment is a flag you did not set.** `CI` is the one you choose; these are set for you,
 and each one silently reroutes the code under test:
 
-| Set by | What it changes |
-| --- | --- |
-| `CLAUDECODE=1`, and the other `CLAUDE*` vars | `isAiAgent()` returns true, and `shouldUseTui()` returns false on it. Every TUI test an agent runs is on the non-TUI path unless the vars are stripped. |
-| The pty you run under | `is-tui-enabled.ts` gates on `process.stdout.columns > 0 && rows > 0`. A pty with no winsize (lefthook's inner pty reports 0x0) refuses the TUI outright. |
-| `SANDBOX_RUNTIME=1` | `isSandbox()` is true inside the review sandbox, which some paths branch on. |
+| Set by                                       | What it changes                                                                                                                                           |
+| -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `CLAUDECODE=1`, and the other `CLAUDE*` vars | `isAiAgent()` returns true, and `shouldUseTui()` returns false on it. Every TUI test an agent runs is on the non-TUI path unless the vars are stripped.   |
+| The pty you run under                        | `is-tui-enabled.ts` gates on `process.stdout.columns > 0 && rows > 0`. A pty with no winsize (lefthook's inner pty reports 0x0) refuses the TUI outright. |
+| `SANDBOX_RUNTIME=1`                          | `isSandbox()` is true inside the review sandbox, which some paths branch on.                                                                              |
 
 The failure is always the same shape: the arm you believe is exercising the feature is quietly
 exercising its absence, and it comes back clean. **Assert the state you are testing rather than

🔔 Heads up, your workspace has pending recommendations ↗ to auto-apply fixes for similar failures.

Apply fix via Nx Cloud  Reject fix via Nx Cloud


Or Apply changes locally with:

npx nx-cloud apply-locally zoMV-g4iE

Apply fix locally with your editor ↗   View interactive diff ↗



🎓 Learn more about Self-Healing CI on nx.dev

Reproducing #36520 needed 160 unattended runs per version, and each of
these cost an iteration before it was understood.

- `tui.autoExit: 0` is a duration, so it means never auto-exit, not exit
  immediately. A loop set that way waits forever for a keypress.
- 22.7.x keeps the TUI open after a task fails and `--tuiAutoExit=true`
  does not override it, so an unattended run hangs on exactly the case
  being measured. Feed it a quit.
- `--tui` and `--outputStyle` are mutually exclusive; passing both prints
  help and exits 1, which reads as the reproduction failing.

Also: size the pty, or the capability check refuses the TUI and the run
measures the non-TUI path again.
@AgentEnder
AgentEnder force-pushed the chore/issue-triage-tooling branch from 7959e71 to 7ed7417 Compare September 2, 2026 03:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant