Summary
templates/workflows/squad-triage.yml presents keyword-matching output as the Lead agent's analysis. The posted comment is titled:
### ποΈ Squad Triage β {lead.name} ({lead.role})
**Assigned to:** ...
**Reason:** Issue relates to frontend/UI work
No agent runs. Reason: is a hardcoded string selected by issueText.includes(...). A reader reasonably concludes the Lead read the issue and reasoned about it.
This matters beyond cosmetics: it makes a low-accuracy signal look like a high-accuracy one, which is exactly when a human stops double-checking.
Specific defects
-
Unbounded substring matching. issueText.includes('ci') matches "specific", "decision", "precision". includes('ui') matches "build", "require", "guide". includes('api') matches "rapid".
-
Unconditional Lead fallback. When nothing matches, the issue is assigned to the Lead with Reason: No specific domain match. There is no "unassigned" or "ambiguous" outcome, so the workflow always produces a confident-looking answer.
-
routing.md is read but never used for routing. routingContent is loaded and then unreferenced; routing uses role keywords hardcoded in the workflow. Teams that carefully curate routing.md get no effect from it.
-
Wrong assignments are self-sealing. A squad:* label suppresses later re-triage, so a bad label is worse than no label β it hides itself and cannot be automatically corrected.
This defect class was already fixed once, elsewhere
#1591 fixed "unbounded substring matching + unconditional Lead fallback" for ralph-triage.js. Both defects remain in the bundled squad-triage.yml. The fix appears not to have been applied across both triage paths.
Evidence and its limits
We replaced this workflow locally with word-boundary matching driven by a routing.md signals table, plus explicit ambiguous / unassigned outcomes instead of a Lead fallback.
Measured: our word-boundary implementation against 14 real issues from our repository, using our live team.md / routing.md. Roughly 4 of 14 routed correctly. One issue about PR tooling was confidently assigned to the crawler specialist because its body mentions "workday" once, in passing.
Not measured: stock squad-triage.yml itself. It uses looser substring matching and ignores routing.md, so we expect it to do no better, but we did not benchmark it and are not claiming a number for it.
Our conclusion was that keyword matching is the wrong instrument for this problem, not that it needed better keywords. We have disabled automatic assignment entirely and reduced our workflow to queueing (opened β add squad), leaving triage to the Lead at session start.
Suggestions
Roughly in order of effort:
- Stop attributing keyword output to the Lead. Even keeping the current logic, retitle the comment so it does not read as agent analysis. This is a small change and independently worthwhile.
- Add word boundaries and an explicit no-match outcome instead of the Lead fallback.
- Actually consume
routing.md, since it is already read.
- Consider LLM-based triage.
actions/ai-inference with permissions: models: read runs on GITHUB_TOKEN with no additional secret, which fits Squad's existing GitHub-native posture. Reading an issue and matching it to a roster is a natural language task; this seems closer to what Squad's own doctrine describes when it says the Lead "reads the issue, analyzes it, and assigns the correct label."
Happy to contribute a PR for 1β3 if that would help.
Found while auditing our local fork of this workflow. Squad CLI 0.13.1.
Summary
templates/workflows/squad-triage.ymlpresents keyword-matching output as the Lead agent's analysis. The posted comment is titled:No agent runs.
Reason:is a hardcoded string selected byissueText.includes(...). A reader reasonably concludes the Lead read the issue and reasoned about it.This matters beyond cosmetics: it makes a low-accuracy signal look like a high-accuracy one, which is exactly when a human stops double-checking.
Specific defects
Unbounded substring matching.
issueText.includes('ci')matches "specific", "decision", "precision".includes('ui')matches "build", "require", "guide".includes('api')matches "rapid".Unconditional Lead fallback. When nothing matches, the issue is assigned to the Lead with
Reason: No specific domain match. There is no "unassigned" or "ambiguous" outcome, so the workflow always produces a confident-looking answer.routing.mdis read but never used for routing.routingContentis loaded and then unreferenced; routing uses role keywords hardcoded in the workflow. Teams that carefully curaterouting.mdget no effect from it.Wrong assignments are self-sealing. A
squad:*label suppresses later re-triage, so a bad label is worse than no label β it hides itself and cannot be automatically corrected.This defect class was already fixed once, elsewhere
#1591 fixed "unbounded substring matching + unconditional Lead fallback" for
ralph-triage.js. Both defects remain in the bundledsquad-triage.yml. The fix appears not to have been applied across both triage paths.Evidence and its limits
We replaced this workflow locally with word-boundary matching driven by a
routing.mdsignals table, plus explicitambiguous/unassignedoutcomes instead of a Lead fallback.Measured: our word-boundary implementation against 14 real issues from our repository, using our live
team.md/routing.md. Roughly 4 of 14 routed correctly. One issue about PR tooling was confidently assigned to the crawler specialist because its body mentions "workday" once, in passing.Not measured: stock
squad-triage.ymlitself. It uses looser substring matching and ignoresrouting.md, so we expect it to do no better, but we did not benchmark it and are not claiming a number for it.Our conclusion was that keyword matching is the wrong instrument for this problem, not that it needed better keywords. We have disabled automatic assignment entirely and reduced our workflow to queueing (
openedβ addsquad), leaving triage to the Lead at session start.Suggestions
Roughly in order of effort:
routing.md, since it is already read.actions/ai-inferencewithpermissions: models: readruns onGITHUB_TOKENwith no additional secret, which fits Squad's existing GitHub-native posture. Reading an issue and matching it to a roster is a natural language task; this seems closer to what Squad's own doctrine describes when it says the Lead "reads the issue, analyzes it, and assigns the correct label."Happy to contribute a PR for 1β3 if that would help.
Found while auditing our local fork of this workflow. Squad CLI 0.13.1.