From aec6d2971f5aaabb4f63c7adb043c7b8b0d96837 Mon Sep 17 00:00:00 2001 From: Hardik Date: Fri, 19 Jun 2026 14:00:44 +0530 Subject: [PATCH 1/2] fix(automation): triage owns routing for every portal issue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Report Issue button (older deployed build) stamps claude-queue at creation, so triage skipped those issues and they went straight to auto-fix (e.g. #37, a large localization feature that should be interactive). Triage now claims a portal issue until it carries a new `triaged` marker (or is in progress/done) — claude-queue is no longer a skip reason. On routing to interactive it strips the stray claude-queue; on claude-queue it adds triaged. Manual queue still works for NON-portal issues (triage never claims those). Resilient regardless of which button build is deployed. Co-Authored-By: Claude Opus 4.8 --- automation/README.md | 22 ++++++++++++++-------- automation/claude-issue-watcher.sh | 18 +++++++++++++++--- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/automation/README.md b/automation/README.md index 8f938cc..576098b 100644 --- a/automation/README.md +++ b/automation/README.md @@ -106,17 +106,23 @@ before a release tag deploys them to prod. ## Issue label lifecycle ``` -portal ──(triage)──▶ claude-queue ─▶ claude-working ─▶ claude-pr | claude-failed - └────▶ interactive (stops here — handle with Claude interactively) +portal ──(triage)──▶ triaged + claude-queue ─▶ claude-working ─▶ claude-pr | claude-failed + └─────▶ triaged + interactive (stops here — handle with Claude interactively) ``` -- A `portal` issue with no decision label yet is triaged once (`maxTriagePerRun` - per run). Triage adds `claude-queue` or `interactive` and posts a breakdown. +- **Triage owns routing for every `portal` issue.** Each untriaged portal issue is + triaged once (`maxTriagePerRun` per run); triage adds `triaged` plus `claude-queue` + or `interactive` and posts a breakdown. Triage skips an issue only once it carries + `triaged`, `interactive`, `claude-working`, `claude-pr`, or `claude-failed`. +- **`claude-queue` alone does NOT skip triage on a portal issue.** The Report Issue + button may stamp `claude-queue` at creation; triage still claims the issue and + decides routing (stripping the stray `claude-queue` if it routes to `interactive`). + This is why triage works even if an older button build is deployed. - `claude-queue` → `claude-working` → `claude-pr` (PR opened) or `claude-failed`. -- To retry a failed issue, re-add `claude-queue`. -- To queue any manually-created issue for Claude (skipping triage), add - `claude-queue` directly. To force human handling, add `interactive`. -- Triage is skipped for issues that already carry any decision label. +- To retry a failed issue, re-add `claude-queue` (and remove `claude-failed`). +- To queue a **non-portal** issue for Claude (skipping triage), add `claude-queue` + directly — triage never claims issues without the `portal` label. +- To force a portal issue straight to fix, add `triaged` + `claude-queue` yourself. ## Releasing diff --git a/automation/claude-issue-watcher.sh b/automation/claude-issue-watcher.sh index 8207aab..9a03b6d 100644 --- a/automation/claude-issue-watcher.sh +++ b/automation/claude-issue-watcher.sh @@ -145,12 +145,16 @@ if [ ! -d "$WORKDIR/.git" ]; then git -C "$WORKDIR" config user.email "claude-autofix@pelagiamarine.com" fi -DECISION_LABELS="claude-queue interactive claude-working claude-pr claude-failed" +# Triage OWNS routing for every portal issue. It claims a portal issue until it has +# been triaged (`triaged`) or is already in progress/done. NOTE: claude-queue is +# deliberately NOT a skip reason — the Report Issue button may stamp claude-queue at +# creation, and triage must still decide claude-queue vs interactive itself. +TRIAGE_SKIP_LABELS="interactive claude-working claude-pr claude-failed triaged" # ===================================================================== # Phase 1: triage new portal issues # ===================================================================== -dl_json=$(printf '%s\n' $DECISION_LABELS | jq -R . | jq -sc .) +dl_json=$(printf '%s\n' $TRIAGE_SKIP_LABELS | jq -R . | jq -sc .) to_triage=$(issues_by_label portal | jq -c --argjson dl "$dl_json" \ '[ .[] | select((.labels|map(.name)) as $have | ($dl | any(. as $d | $have|index($d))) | not) ] | sort_by(.number)') to_triage=$(printf '%s' "$to_triage" | jq -c ".[:$MAX_TRIAGE]") @@ -212,13 +216,21 @@ while [ "$t" -lt "$n_triage" ]; do if [ -z "$label" ]; then log "Triage for #$num produced no valid decision; leaving for a human" + # Mark triaged + strip any button-stamped claude-queue so it is NOT auto-fixed. + set_labels "$num" "claude-queue" "triaged" add_comment "$num" "$BOT_MARKER [Claude triage] Could not auto-triage this issue. A human should review it and add either \`claude-queue\` or \`interactive\`." continue fi # Label FIRST so a comment failure cannot trigger a re-triage that double-posts. - add_labels "$num" "$label" + # Mark `triaged` so triage won't re-claim it. For interactive, strip any + # claude-queue the Report Issue button may have stamped so the fix phase ignores it. + if [ "$label" = "interactive" ]; then + set_labels "$num" "claude-queue" "interactive triaged" + else + add_labels "$num" claude-queue triaged + fi # No bot marker on the breakdown: it is genuine refined requirements and SHOULD # be fed to the fix stage (comments_block includes it). note=${breakdown:-"(no breakdown produced)"} -- 2.45.3 From 520b1527e0a9a4df0a0d47c6154830d3f3547d0c Mon Sep 17 00:00:00 2001 From: Hardik Date: Fri, 19 Jun 2026 14:10:48 +0530 Subject: [PATCH 2/2] feat(automation): triage classifies bug vs feature Triage now writes CLAUDE_TRIAGE_TYPE.txt (bug|feature) and the watcher applies the matching label to every triaged issue (additive). Previously bug/feature labels were never applied by the pipeline. Also shows the type in the triage comment. Co-Authored-By: Claude Opus 4.8 --- automation/README.md | 7 ++++--- automation/claude-issue-watcher.sh | 20 ++++++++++++++++---- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/automation/README.md b/automation/README.md index 576098b..5891a18 100644 --- a/automation/README.md +++ b/automation/README.md @@ -111,9 +111,10 @@ portal ──(triage)──▶ triaged + claude-queue ─▶ claude-working ─ ``` - **Triage owns routing for every `portal` issue.** Each untriaged portal issue is - triaged once (`maxTriagePerRun` per run); triage adds `triaged` plus `claude-queue` - or `interactive` and posts a breakdown. Triage skips an issue only once it carries - `triaged`, `interactive`, `claude-working`, `claude-pr`, or `claude-failed`. + triaged once (`maxTriagePerRun` per run); triage adds `triaged`, a routing label + (`claude-queue` or `interactive`), a type label (`bug` or `feature`), and posts a + breakdown. Triage skips an issue only once it carries `triaged`, `interactive`, + `claude-working`, `claude-pr`, or `claude-failed`. - **`claude-queue` alone does NOT skip triage on a portal issue.** The Report Issue button may stamp `claude-queue` at creation; triage still claims the issue and decides routing (stripping the stray `claude-queue` if it routes to `interactive`). diff --git a/automation/claude-issue-watcher.sh b/automation/claude-issue-watcher.sh index 9a03b6d..aebed13 100644 --- a/automation/claude-issue-watcher.sh +++ b/automation/claude-issue-watcher.sh @@ -171,7 +171,7 @@ while [ "$t" -lt "$n_triage" ]; do log "-- Triaging #$num: $title" reset_clone comments=$(comments_block "$num") - rm -f "$WORKDIR/CLAUDE_TRIAGE_LABEL.txt" "$WORKDIR/CLAUDE_TRIAGE.md" + rm -f "$WORKDIR/CLAUDE_TRIAGE_LABEL.txt" "$WORKDIR/CLAUDE_TRIAGE_TYPE.txt" "$WORKDIR/CLAUDE_TRIAGE.md" prompt_file=$(mktemp) { @@ -192,8 +192,11 @@ while [ "$t" -lt "$n_triage" ]; do printf '%s\n' " - interactive = needs human steering: ambiguous or underspecified, needs business content" printf '%s\n' " or a design decision, a schema migration, permissions/payments changes, an external" printf '%s\n' " dependency, or a large feature needing visual verification." - printf '%s\n' "3. Write TWO files in the repository root, nothing else:" - printf '%s\n' " - CLAUDE_TRIAGE_LABEL.txt -- a single line with EXACTLY one word: claude-queue OR interactive" + printf '%s\n' "3. Classify the issue as a BUG (something is broken / not working as intended) or a" + printf '%s\n' " FEATURE (new capability or a change/enhancement to existing behaviour)." + printf '%s\n' "4. Write THREE files in the repository root, nothing else:" + printf '%s\n' " - CLAUDE_TRIAGE_LABEL.txt -- one line, EXACTLY one word: claude-queue OR interactive" + printf '%s\n' " - CLAUDE_TRIAGE_TYPE.txt -- one line, EXACTLY one word: bug OR feature" printf '%s\n' " - CLAUDE_TRIAGE.md -- your requirements breakdown as markdown: action items, files/areas" printf '%s\n' " involved, open questions, and a final one-line 'Routing rationale: ...'." } > "$prompt_file" @@ -212,8 +215,17 @@ while [ "$t" -lt "$n_triage" ]; do fi breakdown="" [ -f "$WORKDIR/CLAUDE_TRIAGE.md" ] && breakdown=$(cat "$WORKDIR/CLAUDE_TRIAGE.md") + type="" + if [ -f "$WORKDIR/CLAUDE_TRIAGE_TYPE.txt" ]; then + traw=$(cat "$WORKDIR/CLAUDE_TRIAGE_TYPE.txt") + if printf '%s' "$traw" | grep -qiw feature; then type=feature + elif printf '%s' "$traw" | grep -qiw bug; then type=bug; fi + fi reset_clone + # Classify bug/feature regardless of routing outcome (additive, never clears). + [ -n "$type" ] && { add_labels "$num" "$type"; log "Classified #$num as $type"; } + if [ -z "$label" ]; then log "Triage for #$num produced no valid decision; leaving for a human" # Mark triaged + strip any button-stamped claude-queue so it is NOT auto-fixed. @@ -238,7 +250,7 @@ while [ "$t" -lt "$n_triage" ]; do $note -**Routing:** \`$label\`" +**Routing:** \`$label\`${type:+ | **Type:** \`$type\`}" log "Triaged #$num -> $label" done -- 2.45.3