Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 22 additions & 12 deletions .github/workflows/claude-issue-triage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ jobs:

**Scope Boundary:**
- Do not implement a fix, edit repository files, run tests, create a branch, commit, or push.
- The only permitted write actions are adding issue labels and posting issue comments.
- The only permitted write actions are setting triage-owned issue labels and posting
issue comments.
- Even when the issue includes a root cause, suggested fix, or acceptance criteria, stop after triage.

**Issue Analysis:**
Expand All @@ -55,7 +56,6 @@ jobs:
- Enhancement (improvement to existing feature)
- Documentation (docs improvement)
- Question/Support (user help)
- MCP tool issue (specific to MCP functionality)

2. **Priority Assessment:**
- Critical: Security issues, data loss, complete breakage
Expand All @@ -77,22 +77,32 @@ jobs:
- Complex: Major feature work, architectural changes

**Actions to Take:**
1. Add appropriate labels using:
`./scripts/edit-issue-labels.sh --add-label label1 --add-label label2`
1. After finishing the analysis, call the triage helper exactly once:
`./scripts/edit-issue-labels.sh --type TYPE --component COMPONENT`
2. Check for duplicates using: `gh search issues`
3. If duplicate found, comment mentioning the original issue
4. For feature requests, ask clarifying questions if needed
5. For bugs, request reproduction steps if missing

**Available Labels:**
- Type: bug, enhancement, feature, documentation, question, mcp-tool
- Priority: critical, high, medium, low
- Component: cli, mcp, database, cloud, docs, testing
- Complexity: simple, medium, complex
- Status: needs-reproduction, needs-clarification, duplicate
**Triage Helper Contract:**
- TYPE must be exactly one of: `bug`, `enhancement`, `documentation`, `question`.
Map feature requests and improvements to `enhancement`.
Comment thread
phernandez marked this conversation as resolved.
- MCP identifies a component, not a TYPE. Classify an MCP issue by its behavior using
one of the four TYPE values and record the MCP component in prose.
- COMPONENT must be `cloud` only when the issue directly concerns Cloud behavior;
otherwise use `none`. Record other components in prose because they do not have
repository labels.
- Priority and complexity are prose assessments only. They do not have repository
labels.
- If an issue is a duplicate, link the original in the comment. Do not add a
`duplicate` label or close the issue.
- Do not probe labels, try candidate calls, or use the helper for discovery. It is a
mutating command. One final call updates only triage-owned labels and preserves
every label outside that owned set.

Read the issue carefully, apply appropriate labels, post any necessary triage comment,
and then stop. Do not begin implementation work.
Read the issue carefully, apply the triage classification, and post a concise comment.
Do not include tool logs, label-taxonomy discussion, or a task checklist. Then stop;
do not begin implementation work.
claude_args: |
--permission-mode dontAsk
--allowedTools "Bash(./scripts/edit-issue-labels.sh:*),Bash(gh issue view:*),Bash(gh issue comment:*),Bash(gh search issues:*),Read,Grep,Glob"
103 changes: 80 additions & 23 deletions scripts/edit-issue-labels.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env bash

# Limit automated issue triage to labels on the issue that triggered the workflow.
# Apply one semantic triage classification to the issue that triggered the workflow.
set -euo pipefail

issue_number=$(jq -r '.issue.number // empty' "${GITHUB_EVENT_PATH:?GITHUB_EVENT_PATH not set}")
Expand All @@ -9,49 +9,106 @@ if ! [[ "$issue_number" =~ ^[0-9]+$ ]]; then
exit 1
fi

labels=()
type_label=""
component=""
while [[ $# -gt 0 ]]; do
case "$1" in
--add-label)
--type)
if [[ $# -lt 2 ]]; then
echo "Error: --add-label requires a label" >&2
echo "Error: --type requires a value" >&2
exit 1
fi
labels+=("$2")
if [[ -n "$type_label" ]]; then
echo "Error: --type may be provided only once" >&2
exit 1
fi
type_label="$2"
shift 2
;;
--component)
if [[ $# -lt 2 ]]; then
echo "Error: --component requires a value" >&2
exit 1
fi
if [[ -n "$component" ]]; then
echo "Error: --component may be provided only once" >&2
exit 1
fi
component="$2"
shift 2
;;
*)
echo "Error: only --add-label is accepted" >&2
echo "Error: only --type and --component are accepted" >&2
exit 1
;;
esac
done

if [[ ${#labels[@]} -eq 0 ]]; then
echo "Error: at least one label is required" >&2
case "$type_label" in
bug|enhancement|documentation|question) ;;
"")
echo "Error: --type is required" >&2
exit 1
;;
*)
echo "Error: unsupported triage type: $type_label" >&2
exit 1
;;
esac

case "$component" in
cloud|none) ;;
"")
echo "Error: --component is required" >&2
exit 1
;;
*)
echo "Error: unsupported triage component: $component" >&2
exit 1
;;
esac

repository=${GITHUB_REPOSITORY:?GITHUB_REPOSITORY not set}
labels_url="repos/$repository/issues/$issue_number/labels"

# The triage bot owns only the four type labels and the cloud component label. Read current
# labels only to find obsolete bot-owned values; targeted mutations leave every other label
# untouched, including labels added concurrently after this read.
if ! current_labels=$(gh api "repos/$repository/issues/$issue_number" --jq '.labels[].name'); then
echo "Error: unable to read current issue labels" >&2
exit 1
fi

valid_labels=$(gh label list --limit 500 --json name --jq '.[].name')
filtered_labels=()
for label in "${labels[@]}"; do
if grep -qxF "$label" <<<"$valid_labels"; then
filtered_labels+=("$label")
else
echo "Ignoring unknown label: $label" >&2
fi
done
obsolete_labels=()
while IFS= read -r label; do
case "$label" in
"$type_label") ;;
cloud)
if [[ "$component" != "cloud" ]]; then
obsolete_labels+=("$label")
fi
;;
bug|enhancement|documentation|question) obsolete_labels+=("$label") ;;
esac
done <<< "$current_labels"

if [[ ${#filtered_labels[@]} -eq 0 ]]; then
exit 0
desired_labels=("$type_label")
if [[ "$component" == "cloud" ]]; then
desired_labels+=("cloud")
fi

repository=${GITHUB_REPOSITORY:?GITHUB_REPOSITORY not set}
labels_url="repos/$repository/issues/$issue_number/labels"
api_args=(--method POST "$labels_url")
for label in "${filtered_labels[@]}"; do
for label in "${desired_labels[@]}"; do
api_args+=(-f "labels[]=$label")
done

# Add the desired classification before removing obsolete values. A failed additive request
# therefore leaves the prior classification intact; a later rerun can converge after a delete
# failure without losing unrelated labels.
gh api "${api_args[@]}" --silent
Comment thread
phernandez marked this conversation as resolved.
echo "Added: ${filtered_labels[*]}"

for label in "${obsolete_labels[@]}"; do
gh api --method DELETE "$labels_url/$label" --silent
done

echo "Set triage labels: type=$type_label component=$component"
Loading
Loading