fix(automation): triage owns routing for every portal issue #39

Merged
shad0w merged 3 commits from fix/triage-owns-portal-routing into master 2026-06-19 08:45:22 +00:00
2 changed files with 46 additions and 15 deletions

View file

@ -136,17 +136,24 @@ before a release tag deploys them to prod.
## Issue label lifecycle ## Issue label lifecycle
``` ```
portal ──(triage)──▶ claude-queue ─▶ claude-working ─▶ claude-pr | claude-failed portal ──(triage)──▶ triaged + claude-queue ─▶ claude-working ─▶ claude-pr | claude-failed
└────▶ interactive (stops here — handle with Claude interactively) └──── triaged + interactive (stops here — handle with Claude interactively)
``` ```
- A `portal` issue with no decision label yet is triaged once (`maxTriagePerRun` - **Triage owns routing for every `portal` issue.** Each untriaged portal issue is
per run). Triage adds `claude-queue` or `interactive` and posts a breakdown. 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`).
This is why triage works even if an older button build is deployed.
- `claude-queue``claude-working``claude-pr` (PR opened) or `claude-failed`. - `claude-queue``claude-working``claude-pr` (PR opened) or `claude-failed`.
- To retry a failed issue, re-add `claude-queue`. - To retry a failed issue, re-add `claude-queue` (and remove `claude-failed`).
- To queue any manually-created issue for Claude (skipping triage), add - To queue a **non-portal** issue for Claude (skipping triage), add `claude-queue`
`claude-queue` directly. To force human handling, add `interactive`. directly — triage never claims issues without the `portal` label.
- Triage is skipped for issues that already carry any decision label. - To force a portal issue straight to fix, add `triaged` + `claude-queue` yourself.
## Releasing ## Releasing

View file

@ -145,12 +145,16 @@ if [ ! -d "$WORKDIR/.git" ]; then
git -C "$WORKDIR" config user.email "claude-autofix@pelagiamarine.com" git -C "$WORKDIR" config user.email "claude-autofix@pelagiamarine.com"
fi 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 # 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" \ 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)') '[ .[] | 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]") to_triage=$(printf '%s' "$to_triage" | jq -c ".[:$MAX_TRIAGE]")
@ -167,7 +171,7 @@ while [ "$t" -lt "$n_triage" ]; do
log "-- Triaging #$num: $title" log "-- Triaging #$num: $title"
reset_clone reset_clone
comments=$(comments_block "$num") 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) prompt_file=$(mktemp)
{ {
@ -188,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' " - 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' " 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' " dependency, or a large feature needing visual verification."
printf '%s\n' "3. Write TWO files in the repository root, nothing else:" printf '%s\n' "3. Classify the issue as a BUG (something is broken / not working as intended) or a"
printf '%s\n' " - CLAUDE_TRIAGE_LABEL.txt -- a single line with EXACTLY one word: claude-queue OR interactive" 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' " - 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: ...'." printf '%s\n' " involved, open questions, and a final one-line 'Routing rationale: ...'."
} > "$prompt_file" } > "$prompt_file"
@ -208,17 +215,34 @@ while [ "$t" -lt "$n_triage" ]; do
fi fi
breakdown="" breakdown=""
[ -f "$WORKDIR/CLAUDE_TRIAGE.md" ] && breakdown=$(cat "$WORKDIR/CLAUDE_TRIAGE.md") [ -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 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 if [ -z "$label" ]; then
log "Triage for #$num produced no valid decision; leaving for a human" 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 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\`." [Claude triage] Could not auto-triage this issue. A human should review it and add either \`claude-queue\` or \`interactive\`."
continue continue
fi fi
# Label FIRST so a comment failure cannot trigger a re-triage that double-posts. # 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 # No bot marker on the breakdown: it is genuine refined requirements and SHOULD
# be fed to the fix stage (comments_block includes it). # be fed to the fix stage (comments_block includes it).
note=${breakdown:-"(no breakdown produced)"} note=${breakdown:-"(no breakdown produced)"}
@ -226,7 +250,7 @@ while [ "$t" -lt "$n_triage" ]; do
$note $note
**Routing:** \`$label\`" **Routing:** \`$label\`${type:+ | **Type:** \`$type\`}"
log "Triaged #$num -> $label" log "Triaged #$num -> $label"
done done