feat(ci): auto-triage new issues with Claude (label + comment) - #221
Conversation
On a new/reopened issue, Claude (Haiku 4.5) classifies it (category / priority / area) against an enforced JSON schema, applies the labels, and posts a short triage comment so issues arrive pre-sorted. - .github/workflows/issue-triage.yml β runs on issues:[opened,reopened], issues:write. - scripts/issue-triage.mjs β reads the event payload (no shell interpolation), treats issue text as data not instructions, labels + comments via gh; triage errors are non-blocking (never affect the issue). Requires repo secret ANTHROPIC_API_KEY. Note: issues-triggered workflows run only from the default branch, so this activates once it reaches main.
π WalkthroughWalkthroughAdds an automated issue triage system consisting of a GitHub Actions workflow ( ChangesIssue Triage Automation
Estimated code review effortπ― 2 (Simple) | β±οΈ ~10 minutes Poem
π₯ Pre-merge checks | β 3 | β 2β Failed checks (2 warnings)
β Passed checks (3 passed)
βοΈ Tip: You can configure your own custom pre-merge checks in the settings. β¨ Finishing Touchesπ Generate docstrings
π§ͺ Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. π§ ESLint
ESLint install failed: dependency version conflict. Check your lock file or package.json. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
π€ Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/issue-triage.yml:
- Around line 27-30: The setup-node action reference uses a mutable version tag
(`@v4`) which creates a supply-chain security risk. Replace the mutable tag
reference in the actions/setup-node action with an immutable commit SHA instead.
This ensures the workflow always uses a specific, pinned version of the action
rather than the latest version matching that tag, which could change
unexpectedly.
- Around line 32-33: The npm install command for the Anthropic SDK in the
"Install Anthropic SDK" step lacks a pinned version, which allows npm to install
potentially different versions on each workflow run, creating inconsistency and
potential breaking changes. Modify the run command to specify a fixed version of
the `@anthropic-ai/sdk` package by appending the version number to the package
name (e.g., `@anthropic-ai/sdk`@X.Y.Z) so that the same version is consistently
installed across all workflow executions.
- Around line 24-25: The `actions/checkout@v4` action uses a mutable tag which
poses a supply-chain attack risk, and is missing the `persist-credentials:
false` parameter which could leak the GITHUB_TOKEN. Replace the `@v4` tag
reference with a specific immutable commit SHA (for example `@<commit-sha>`) and
add a new line with `persist-credentials: false` to the checkout action to
ensure credential isolation and protect against token exposure.
- Around line 10-12: Add brief inline comments to explain the purpose of each
permission in the permissions block for the issue-triage workflow. For the
"issues: write" permission, add a comment explaining it's needed for creating or
updating issues. For the "contents: read" permission, add a comment explaining
it's needed for reading repository content. These comments should appear on the
same line or immediately above each permission line to improve code
maintainability and clarify why each permission is required.
In `@scripts/issue-triage.mjs`:
- Line 28: The summary field in the schema has a description mentioning the
140-character limit but lacks actual schema enforcement. Add a maxLength
property set to 140 in the summary field schema definition (where type is
"string") to enforce the character limit during schema validation rather than
relying solely on the description text. This ensures the AI-generated summaries
are validated to be concise at the schema level.
- Line 70: The prioColor variable assignment uses nested ternary operators which
are difficult to read and maintain. Replace this with a Map or object that maps
priority levels (high, medium, and low/default) to their corresponding color
codes (b60205, fbca04, and 0e8a16), then perform a simple lookup using the
t.priority value to assign the appropriate color. This approach improves code
readability and makes it easier to add or modify priority-to-color mappings in
the future.
- Around line 16-18: The script currently accesses event.issue and issue.number
without validating that the event payload contains the expected structure, which
will cause the script to crash if event.issue is missing or null. Add defensive
checks after parsing the event to verify that event.issue exists before
attempting to access issue.number. You can add a guard condition that checks if
event and event.issue are defined, and either log an error and exit gracefully,
or throw a descriptive error if the required properties are missing from the
GitHub event payload.
- Around line 59-60: After parsing the JSON response into variable t using
JSON.parse(text), add validation logic to verify that the parsed object contains
the required properties (priority, category, and any other properties used
downstream) before attempting to use them. If any required properties are
missing or the parsed object is empty (like when it defaults to {}), either
provide sensible default values or skip processing the response entirely. This
prevents accessing undefined properties that would result in malformed labels
being created downstream.
πͺ Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
βΉοΈ Review info
βοΈ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: cf16c40f-819b-40c6-92d0-e033a2a658e9
π Files selected for processing (2)
.github/workflows/issue-triage.ymlscripts/issue-triage.mjs
| permissions: | ||
| issues: write | ||
| contents: read |
There was a problem hiding this comment.
π§Ή Nitpick | π΅ Trivial | β‘ Quick win
Add explanatory comments for workflow permissions.
While the permissions are correctly scoped, adding brief inline comments would improve maintainability and address the static analysis hint.
π Suggested improvement
permissions:
- issues: write
- contents: read
+ issues: write # Apply labels and post triage comment
+ contents: read # Checkout the repository to access scripts/π Committable suggestion
βΌοΈ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| permissions: | |
| issues: write | |
| contents: read | |
| permissions: | |
| issues: write # Apply labels and post triage comment | |
| contents: read # Checkout the repository to access scripts/ |
π§° Tools
πͺ zizmor (1.25.2)
[error] 11-11: overly broad permissions (excessive-permissions): issues: write is overly broad at the workflow level
(excessive-permissions)
[warning] 11-11: permissions without explanatory comments (undocumented-permissions): needs an explanatory comment
(undocumented-permissions)
π€ Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/issue-triage.yml around lines 10 - 12, Add brief inline
comments to explain the purpose of each permission in the permissions block for
the issue-triage workflow. For the "issues: write" permission, add a comment
explaining it's needed for creating or updating issues. For the "contents: read"
permission, add a comment explaining it's needed for reading repository content.
These comments should appear on the same line or immediately above each
permission line to improve code maintainability and clarify why each permission
is required.
Source: Linters/SAST tools
| - name: Checkout | ||
| uses: actions/checkout@v4 |
There was a problem hiding this comment.
Pin actions to commit SHA and disable credential persistence.
Two security posture gaps:
- The action reference uses a mutable tag (
@v4) instead of an immutable commit SHA, allowing potential supply-chain attacks if the tag is moved. - Missing
persist-credentials: falsecould leak theGITHUB_TOKENthrough Actions artifacts or the working directory.
π Recommended fix
- name: Checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ with:
+ persist-credentials: falseπ Committable suggestion
βΌοΈ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Checkout | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| with: | |
| persist-credentials: false |
π§° Tools
πͺ zizmor (1.25.2)
[warning] 24-25: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[error] 25-25: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
π€ Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/issue-triage.yml around lines 24 - 25, The
`actions/checkout@v4` action uses a mutable tag which poses a supply-chain
attack risk, and is missing the `persist-credentials: false` parameter which
could leak the GITHUB_TOKEN. Replace the `@v4` tag reference with a specific
immutable commit SHA (for example `@<commit-sha>`) and add a new line with
`persist-credentials: false` to the checkout action to ensure credential
isolation and protect against token exposure.
Source: Linters/SAST tools
| - name: Setup Node | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: 22 |
There was a problem hiding this comment.
Pin setup-node action to commit SHA.
The action reference uses a mutable tag (@v4) instead of an immutable commit SHA, creating supply-chain risk.
π Recommended fix
- name: Setup Node
- uses: actions/setup-node@v4
+ uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
node-version: 22π Committable suggestion
βΌοΈ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - name: Setup Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| - name: Setup Node | |
| uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 | |
| with: | |
| node-version: 22 |
π§° Tools
πͺ zizmor (1.25.2)
[error] 28-28: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
π€ Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/issue-triage.yml around lines 27 - 30, The setup-node
action reference uses a mutable version tag (`@v4`) which creates a supply-chain
security risk. Replace the mutable tag reference in the actions/setup-node
action with an immutable commit SHA instead. This ensures the workflow always
uses a specific, pinned version of the action rather than the latest version
matching that tag, which could change unexpectedly.
Source: Linters/SAST tools
| - name: Install Anthropic SDK | ||
| run: npm install --no-save @anthropic-ai/sdk |
There was a problem hiding this comment.
π§Ή Nitpick | π΅ Trivial | β‘ Quick win
Pin the Anthropic SDK version for reproducibility.
Installing the latest version on each run may introduce breaking changes unexpectedly. Pinning to a specific version ensures consistent behavior.
π Suggested improvement
- name: Install Anthropic SDK
- run: npm install --no-save `@anthropic-ai/sdk`
+ run: npm install --no-save `@anthropic-ai/sdk`@0.32.1Check the latest stable version before pinning.
π Committable suggestion
βΌοΈ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - name: Install Anthropic SDK | |
| run: npm install --no-save @anthropic-ai/sdk | |
| - name: Install Anthropic SDK | |
| run: npm install --no-save `@anthropic-ai/sdk`@0.32.1 |
π€ Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/issue-triage.yml around lines 32 - 33, The npm install
command for the Anthropic SDK in the "Install Anthropic SDK" step lacks a pinned
version, which allows npm to install potentially different versions on each
workflow run, creating inconsistency and potential breaking changes. Modify the
run command to specify a fixed version of the `@anthropic-ai/sdk` package by
appending the version number to the package name (e.g., `@anthropic-ai/sdk`@X.Y.Z)
so that the same version is consistently installed across all workflow
executions.
| const event = JSON.parse(fs.readFileSync(process.env.GITHUB_EVENT_PATH, "utf8")); | ||
| const issue = event.issue; | ||
| const number = issue.number; |
There was a problem hiding this comment.
Add validation for the event payload structure.
If the event payload is malformed or event.issue is missing, the script will crash when accessing issue.number. While unlikely in the GitHub Actions environment, adding a guard improves robustness.
π‘οΈ Suggested defensive check
const event = JSON.parse(fs.readFileSync(process.env.GITHUB_EVENT_PATH, "utf8"));
const issue = event.issue;
+if (!issue) {
+ console.error("No issue found in event payload");
+ process.exit(0);
+}
const number = issue.number;π Committable suggestion
βΌοΈ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const event = JSON.parse(fs.readFileSync(process.env.GITHUB_EVENT_PATH, "utf8")); | |
| const issue = event.issue; | |
| const number = issue.number; | |
| const event = JSON.parse(fs.readFileSync(process.env.GITHUB_EVENT_PATH, "utf8")); | |
| const issue = event.issue; | |
| if (!issue) { | |
| console.error("No issue found in event payload"); | |
| process.exit(0); | |
| } | |
| const number = issue.number; |
π€ Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/issue-triage.mjs` around lines 16 - 18, The script currently accesses
event.issue and issue.number without validating that the event payload contains
the expected structure, which will cause the script to crash if event.issue is
missing or null. Add defensive checks after parsing the event to verify that
event.issue exists before attempting to access issue.number. You can add a guard
condition that checks if event and event.issue are defined, and either log an
error and exit gracefully, or throw a descriptive error if the required
properties are missing from the GitHub event payload.
| category: { type: "string", enum: ["bug", "enhancement", "documentation", "question", "invalid"] }, | ||
| priority: { type: "string", enum: ["high", "medium", "low"] }, | ||
| area: { type: "string", enum: ["install", "ui", "ci-e2e", "gateway", "docs", "other"] }, | ||
| summary: { type: "string", description: "One plain-language sentence, <=140 chars." }, |
There was a problem hiding this comment.
π§Ή Nitpick | π΅ Trivial | π€ Low value
Consider enforcing the 140-character limit in the schema.
The description mentions "<=140 chars" but the schema doesn't enforce maxLength. Adding the constraint would ensure the AI-generated summary is always concise.
π§ Optional schema enhancement
- summary: { type: "string", description: "One plain-language sentence, <=140 chars." },
+ summary: { type: "string", maxLength: 140, description: "One plain-language sentence." },π Committable suggestion
βΌοΈ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| summary: { type: "string", description: "One plain-language sentence, <=140 chars." }, | |
| summary: { type: "string", maxLength: 140, description: "One plain-language sentence." }, |
π€ Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/issue-triage.mjs` at line 28, The summary field in the schema has a
description mentioning the 140-character limit but lacks actual schema
enforcement. Add a maxLength property set to 140 in the summary field schema
definition (where type is "string") to enforce the character limit during schema
validation rather than relying solely on the description text. This ensures the
AI-generated summaries are validated to be concise at the schema level.
| const text = resp.content.find((b) => b.type === "text")?.text ?? "{}"; | ||
| const t = JSON.parse(text); |
There was a problem hiding this comment.
Validate the parsed response against the schema.
If the API returns an unexpected response or JSON parsing falls back to {}, accessing t.priority, t.category, etc. will yield undefined, causing label creation to fail or create malformed labels like "priority: undefined".
π‘οΈ Suggested validation
const text = resp.content.find((b) => b.type === "text")?.text ?? "{}";
const t = JSON.parse(text);
+
+// Validate required fields exist
+const required = ["category", "priority", "area", "summary", "suggested_action"];
+for (const field of required) {
+ if (!t[field]) {
+ throw new Error(`Missing required field: ${field}`);
+ }
+}π Committable suggestion
βΌοΈ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const text = resp.content.find((b) => b.type === "text")?.text ?? "{}"; | |
| const t = JSON.parse(text); | |
| const text = resp.content.find((b) => b.type === "text")?.text ?? "{}"; | |
| const t = JSON.parse(text); | |
| // Validate required fields exist | |
| const required = ["category", "priority", "area", "summary", "suggested_action"]; | |
| for (const field of required) { | |
| if (!t[field]) { | |
| throw new Error(`Missing required field: ${field}`); | |
| } | |
| } |
π€ Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/issue-triage.mjs` around lines 59 - 60, After parsing the JSON
response into variable t using JSON.parse(text), add validation logic to verify
that the parsed object contains the required properties (priority, category, and
any other properties used downstream) before attempting to use them. If any
required properties are missing or the parsed object is empty (like when it
defaults to {}), either provide sensible default values or skip processing the
response entirely. This prevents accessing undefined properties that would
result in malformed labels being created downstream.
| /* label already exists β fine */ | ||
| } | ||
| }; | ||
| const prioColor = t.priority === "high" ? "b60205" : t.priority === "medium" ? "fbca04" : "0e8a16"; |
There was a problem hiding this comment.
π§Ή Nitpick | π΅ Trivial | β‘ Quick win
Consider using a map for priority color lookup.
The nested ternary is correct but harder to read and maintain. A map would be clearer.
β»οΈ Suggested refactor
- const prioColor = t.priority === "high" ? "b60205" : t.priority === "medium" ? "fbca04" : "0e8a16";
+ const priorityColors = { high: "b60205", medium: "fbca04", low: "0e8a16" };
+ const prioColor = priorityColors[t.priority] ?? "0e8a16";
ensure(`priority: ${t.priority}`, prioColor, "Auto-triage priority");π Committable suggestion
βΌοΈ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const prioColor = t.priority === "high" ? "b60205" : t.priority === "medium" ? "fbca04" : "0e8a16"; | |
| const priorityColors = { high: "b60205", medium: "fbca04", low: "0e8a16" }; | |
| const prioColor = priorityColors[t.priority] ?? "0e8a16"; | |
| ensure(`priority: ${t.priority}`, prioColor, "Auto-triage priority"); |
π€ Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/issue-triage.mjs` at line 70, The prioColor variable assignment uses
nested ternary operators which are difficult to read and maintain. Replace this
with a Map or object that maps priority levels (high, medium, and low/default)
to their corresponding color codes (b60205, fbca04, and 0e8a16), then perform a
simple lookup using the t.priority value to assign the appropriate color. This
approach improves code readability and makes it easier to add or modify
priority-to-color mappings in the future.
β¦227) Nine follow-ups from CodeRabbit's review of the v3.1.5 payload (already on beta): - gateway-pre-start.sh: gate the openai->codex migration on a USABLE codex JWT read from auth-profiles.json (openclaw.json holds only metadata), so an unauthenticated codex profile can't strand the device by dropping openai; accept legacy openai-codex:default. (#224) - gateway-pre-start.sh: require the full OAuth set (access+refresh+JWT id) before writing/preserving codex auth.json β partial files fail after token expiry. (#222) - issue-triage.mjs: ensure the category label exists before applying (gh issue edit fails atomically on any missing label). (#221) - issue-triage.yml: persist-credentials:false on checkout; pin @anthropic-ai/sdk. (#221) - reset/route.ts: return 409 when the reset doesn't start. (#223) - SystemUpdateApp.tsx: move autoFocus off the destructive reset button to Cancel. (#223) - updater.ts: only persist the channel pin after startUpdate() accepts. (#223) - updater.test.ts: add the ahead-only divergence case. (#223) Validated on a real Jetson via synthetic harnesses (migration gate: JWT->migrate, non-JWT/missing->skip, legacy key; full-OAuth-set health). /simplify-reviewed.
#238) * chore: activate community files + issue triage on main; add Dependabot The community-health files (#219) and the issue-triage bot (#221) were merged to beta but never reached main β and GitHub only reads the default branch: the community profile sat at 62% reporting CoC/SECURITY/issue templates missing, and 'issues:'-triggered workflows never fire from non-default branches, so the triage bot has never run (all open issues are unlabeled). - CODE_OF_CONDUCT.md, SECURITY.md, .github/ISSUE_TEMPLATE/* β verbatim from beta (post-#227 review state) - issue-triage.yml + scripts/issue-triage.mjs β verbatim from beta; NOTE: needs the ANTHROPIC_API_KEY repo secret (not currently set) to classify; until then it no-ops gracefully - NEW .github/dependabot.yml β weekly npm + github-actions updates targeting beta, grouped minor/patch, 'dependencies' label. Dependabot ALERTS also need the Settings toggle (admin). * chore: review fixes β bun-aware Dependabot, scoped SDK install, label guard - dependabot.yml rewritten for the repo's real lockfile situation: bun.lock is authoritative (CI runs 'bun install --frozen-lockfile'), so npm version-update PRs could never go green and wouldn't ship even if merged. Now: bun ecosystem for version updates (weekly, grouped, -> beta), npm kept at open-pull-requests-limit: 0 purely for security-update PRs (bun ecosystem doesn't support them), actions unchanged. Documented the target-branch nuance: security PRs always target main and only bump package-lock.json - refresh bun.lock before merging one. - issue-triage.yml: npm install scoped with --prefix scripts - installing at the repo root reifies the whole tree (node-pty gyp + Playwright browser downloads, minutes per issue, flake risk); scoped it's ~5s. - issue-triage.mjs: throw on missing model text block instead of defaulting to '{}', which would create labels literally named 'undefined'; the outer catch still exits 0 so issue creation is never blocked. * chore: CodeRabbit fixes β job-scoped permissions, SHA-pinned actions, logged label ensure - permissions moved from workflow to job scope (future jobs won't inherit issues:write) - actions/checkout + actions/setup-node pinned to commit SHAs (workflow runs on every opened issue with a paid API secret in scope) - label ensure() logs the swallowed error message so real failures (auth, rate limit) are diagnosable
What
Auto-triage new GitHub issues with Claude so they arrive pre-sorted. On a new (or reopened) issue, Claude (Haiku 4.5) classifies it against an enforced JSON schema and the workflow:
bug/enhancement/documentation/question/invalid)priority:label (high / medium / low) and anarea:label (install / ui / ci-e2e / gateway / docs / other) β auto-created on first runSo GitHub's new-issue notification already carries the categorization.
Files
.github/workflows/issue-triage.ymlβ runs onissues: [opened, reopened], scoped toissues: write; installs the Anthropic SDK; skips bot-opened issues.scripts/issue-triage.mjsβ reads the issue from the event payload (no shell interpolation), treats issue text strictly as data, not instructions (prompt-injection guard), labels + comments viagh. Any triage failure is non-blocking β it logs and exits clean, never affecting the issue.Setup / notes
ANTHROPIC_API_KEY.MODELconstant toclaude-opus-4-8for higher accuracy.issues:-triggered workflows run only from the default branch, so this goes live once it reachesmain.Summary by CodeRabbit