Repository navigation
feat: implement issue #232 — Phase 1: Issue Triage gh-aw workflow #284
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 31 commits
961e1cc
b4abacf
a2ad3dd
568b293
a563cc7
a95a13f
765c03b
14872f7
86792aa
bbc4b28
43c8ade
73d9979
76b0b2e
44dd640
84798b9
a7aff0a
b9da230
e606888
94a474c
b6e9ac2
159c1ba
a87caba
5ba8f6c
b56a45f
4d29d08
3835418
4e7ba5a
78fee82
b31476b
231cf47
c813b80
e57f55c
3ac105a
a82c836
1802171
5da1d8f
2608183
64c8594
da22717
306fe58
5eac9c3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| name: Issue Triage | ||
|
|
||
| on: | ||
| issues: | ||
| types: [opened, reopened] | ||
|
|
||
| # Two-job pattern: triage runs with read-only issue access and produces a | ||
| # staged JSON artifact; apply runs with write access and calls safe-output. | ||
| jobs: | ||
| triage: | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 5 | ||
| permissions: | ||
| contents: read | ||
| issues: read | ||
|
don-petry marked this conversation as resolved.
|
||
| outputs: | ||
| result: ${{ steps.run.outputs.result }} | ||
| skip: ${{ steps.run.outputs.skip }} | ||
| steps: | ||
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | ||
|
coderabbitai[bot] marked this conversation as resolved.
don-petry marked this conversation as resolved.
|
||
| with: | ||
| persist-credentials: false | ||
|
|
||
| - name: Install claude CLI | ||
| uses: anthropics/claude-code-action/setup@24492741e0ccfdef4c1d19da8e11e0f373d07494 # main | ||
| with: | ||
| claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} | ||
|
|
||
| - name: Install Python dependencies | ||
| run: pip install "pyyaml==6.0.3" --only-binary :all: --quiet | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| - name: Run issue-triage (staged) | ||
| id: run | ||
| env: | ||
| ISSUE_NUMBER: ${{ github.event.issue.number }} | ||
| ISSUE_TITLE: ${{ github.event.issue.title }} | ||
| ISSUE_BODY: ${{ github.event.issue.body }} | ||
| ISSUE_LABELS: ${{ join(github.event.issue.labels.*.name, ',') }} | ||
| ISSUE_URL: ${{ github.event.issue.html_url }} | ||
| ISSUE_ACTION: ${{ github.event.action }} | ||
| GITHUB_REPOSITORY: ${{ github.repository }} | ||
|
don-petry marked this conversation as resolved.
|
||
| run: | | ||
| set -euo pipefail | ||
| result=$(bash scripts/aw.sh run issue-triage --staged) | ||
| skip=$(AW_RESULT="$result" python3 - <<'PYEOF' | ||
| import json, os | ||
| d = json.loads(os.environ['AW_RESULT']) | ||
| print('true' if d.get('skip') else 'false') | ||
| PYEOF | ||
| ) | ||
| # Store encoded to survive multi-line JSON in GITHUB_OUTPUT | ||
| encoded=$(printf '%s\n' "$result" | base64 -w0) | ||
| echo "result=$encoded" >> "$GITHUB_OUTPUT" | ||
| echo "skip=$skip" >> "$GITHUB_OUTPUT" | ||
|
|
||
| apply: | ||
| needs: triage | ||
| if: needs.triage.outputs.skip != 'true' | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 3 | ||
| permissions: | ||
| contents: read | ||
| issues: write | ||
|
don-petry marked this conversation as resolved.
|
||
| steps: | ||
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | ||
| with: | ||
| persist-credentials: false | ||
|
|
||
| - name: Install Python dependencies | ||
| run: pip install "pyyaml==6.0.3" --only-binary :all: --quiet | ||
|
|
||
| - name: Apply triage output (safe-output) | ||
| env: | ||
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| GITHUB_REPOSITORY: ${{ github.repository }} | ||
| ISSUE_NUMBER: ${{ github.event.issue.number }} | ||
| TRIAGE_RESULT: ${{ needs.triage.outputs.result }} | ||
| run: | | ||
| result_file=$(mktemp) | ||
| printf '%s\n' "$TRIAGE_RESULT" | base64 -d > "$result_file" | ||
| bash scripts/aw.sh safe-output apply issue-triage "$result_file" | ||
| rm -f "$result_file" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| --- | ||
| name: issue-triage | ||
| trigger: | ||
| issues: | ||
| types: [opened] | ||
| engine: claude-sonnet-4-6 | ||
| permissions: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Useful? React with 👍 / 👎. |
||
| issues: read | ||
| output: staged | ||
| allowed-labels: | ||
| - bug | ||
| - enhancement | ||
| - documentation | ||
| - question | ||
| - needs-triage | ||
| - good-first-issue | ||
| - security | ||
| max-labels: 3 | ||
| --- | ||
|
|
||
| # Issue Triage | ||
|
|
||
| You are triaging a new GitHub issue for the `${REPO}` repository. | ||
|
|
||
| ## Context | ||
|
|
||
| - **Repository:** `${REPO}` | ||
| - **Issue:** [#${ISSUE_NUMBER}](${ISSUE_URL}): ${ISSUE_TITLE} | ||
| - **Labels already applied:** ${ISSUE_LABELS} | ||
|
don-petry marked this conversation as resolved.
|
||
|
|
||
| **Issue body:** | ||
|
|
||
| ${ISSUE_BODY} | ||
|
|
||
| ## Task | ||
|
|
||
| If the issue already has **2 or more labels**, output `{"skip": true}` and stop — do not classify, do not comment. | ||
|
don-petry marked this conversation as resolved.
|
||
|
|
||
| Otherwise: | ||
|
|
||
| 1. **Classify** the issue into the best-fit category: | ||
| - `bug` — something is not working as expected | ||
| - `enhancement` — request for new or extended functionality | ||
| - `documentation` — unclear, missing, or incorrect docs | ||
| - `question` — user asking how to do something | ||
| - `security` — potential security vulnerability | ||
| - `good-first-issue` — simple enough for a first-time contributor | ||
|
|
||
| 2. **Select ≤ 3 labels** from the allowed set: | ||
| `bug`, `enhancement`, `documentation`, `question`, `needs-triage`, | ||
| `good-first-issue`, `security`. | ||
| - Add `needs-triage` for bugs and ambiguous reports that need human review. | ||
| - Add `good-first-issue` only when the scope is clearly small and | ||
| self-contained. | ||
|
|
||
| 3. **Write one welcoming comment** that: | ||
| - Opens warmly and thanks the contributor. | ||
| - Asks the **single most important** clarifying question for this issue type: | ||
| - Bug → steps to reproduce + expected vs actual behaviour | ||
| - Feature → use case / motivation | ||
| - Question → what docs they checked and where they got stuck | ||
| - Documentation → which page or section is unclear and what they expected | ||
| - Security → (do not ask for details publicly; ask them to use the | ||
| private security advisory channel) | ||
| - Does **not** ask multiple unrelated questions. | ||
| - Is concise (≤ 5 sentences). | ||
|
|
||
| ## Output format | ||
|
|
||
| Output **exactly one** JSON object — no markdown fences, no preamble: | ||
|
|
||
| For a normal triage: | ||
| {"labels": ["label1", "label2"], "comment": "Your welcoming comment here."} | ||
|
|
||
| For a skip (issue already has 2+ labels): | ||
| {"skip": true} | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| # Issue Triage — Agentic Workflow | ||
|
|
||
| Part of the GitHub Agentic Workflows rollout (internal discussion: [petry-projects/.github-private#228](https://github.com/petry-projects/.github-private/discussions/228) — requires repo access). | ||
|
|
||
| ## What it does | ||
|
|
||
| When a new issue is opened or reopened, this workflow: | ||
|
|
||
| 1. Checks the existing label count. If the issue already has **2 or more labels** it exits silently (no-op). | ||
| 2. Calls Claude (`claude-sonnet-4-6`) with the issue title and body to classify the issue. | ||
| 3. Selects up to 3 labels from the allowed set and writes a single welcoming comment that asks the most relevant clarifying question. | ||
|
|
||
| All of this happens within 60 seconds of the issue being opened. | ||
|
|
||
| ## Allowed labels | ||
|
|
||
| `bug`, `enhancement`, `documentation`, `question`, `needs-triage`, `good-first-issue`, `security` | ||
|
|
||
| ## Files | ||
|
|
||
| | File | Purpose | | ||
| |------|---------| | ||
| | `.github/workflows/issue-triage.md` | Workflow definition (frontmatter + Claude prompt template) | | ||
| | `.github/workflows/issue-triage-runner.yml` | GitHub Actions runner (two-job: triage → apply) | | ||
| | `scripts/aw.sh` | `gh aw` CLI — compile, run, safe-output | | ||
| | `tests/aw/issue-triage/scenarios.md` | Scenario spec used to validate behaviour | | ||
|
|
||
| ## Architecture | ||
|
|
||
| ``` | ||
| issues: opened/reopened | ||
| │ | ||
| ▼ | ||
| triage job (permissions: issues: read) | ||
| │ bash scripts/aw.sh run issue-triage --staged | ||
| │ → Claude classifies, returns JSON | ||
| ▼ | ||
| apply job (permissions: issues: write) | ||
| │ bash scripts/aw.sh safe-output apply issue-triage result.json | ||
| │ → validates labels, posts comment, adds labels | ||
| ▼ | ||
| issue updated | ||
| ``` | ||
|
|
||
| The two-job split is deliberate: Claude runs with the minimum permission it | ||
| needs (read), and writes are applied by a separate job after the output has | ||
| been validated by `safe-output`. | ||
|
|
||
| ## Validation | ||
|
|
||
| `gh aw compile` validates every `.md` workflow definition file. It checks: | ||
|
|
||
| - Required frontmatter fields: `name`, `trigger`, `engine`, `permissions` | ||
| - Engine is a known Claude model identifier | ||
| - `output` is `staged` or `live` (if present) | ||
| - Trigger is a non-empty event mapping | ||
| - Workflow body (instructions) is not empty | ||
|
|
||
| Run locally: | ||
|
|
||
| ```bash | ||
| bash scripts/aw.sh compile .github/workflows/issue-triage.md | ||
| ``` | ||
|
|
||
| ## Staged vs live mode | ||
|
|
||
| The `output` frontmatter field controls how results are applied: | ||
|
|
||
| | Value | Behaviour | | ||
| |-------|-----------| | ||
| | `staged` | `aw run` emits JSON to stdout; a separate `safe-output apply` step writes to GitHub | | ||
| | `live` (default when omitted) | `aw run` applies labels and posts the comment inline | | ||
|
|
||
| The runner workflow always uses the two-job pattern regardless of this setting, | ||
| so `safe-output` always validates before writing. | ||
|
|
||
| ## Skip condition | ||
|
|
||
| If the issue already has **2 or more labels** when the workflow fires, `aw run` | ||
| returns `{"skip": true}` immediately and the apply job is skipped. No API | ||
| writes occur. | ||
|
|
||
| ## Smoke-test procedure | ||
|
|
||
| To validate against the scenario spec before going live: | ||
|
|
||
| ```bash | ||
| # Create a test issue in a non-critical repo and capture the number | ||
| ISSUE_NUMBER=<n> \ | ||
| ISSUE_TITLE="Login button is broken" \ | ||
| ISSUE_BODY="When I click nothing happens." \ | ||
| ISSUE_LABELS="" \ | ||
| ISSUE_URL="https://github.com/petry-projects/.github-private/issues/<n>" \ | ||
| GITHUB_REPOSITORY="petry-projects/.github-private" \ | ||
| bash scripts/aw.sh run issue-triage --staged | ||
| ``` | ||
|
|
||
| Compare the JSON output against the expected labels and comment style in | ||
| `tests/aw/issue-triage/scenarios.md`. |
Uh oh!
There was an error while loading. Please reload this page.