Skip to content

fix(triage): slugify agent names for labels + pre-create missing labels#785

Merged
tamirdresher merged 1 commit intodevfrom
squad/784-fix-triage-slug
Apr 3, 2026
Merged

fix(triage): slugify agent names for labels + pre-create missing labels#785
tamirdresher merged 1 commit intodevfrom
squad/784-fix-triage-slug

Conversation

@tamirdresher
Copy link
Copy Markdown
Collaborator

Fix: Triage label slug + missing label pre-creation

Fixes the root cause of squad watch showing Untriaged: 1 forever without assigning.

Bug 1: Wrong label slug

Watch triage derived labels from display names instead of directory slugs:

  • "Steve Rogers"squad:steve rogers (space, doesn't exist)
  • "Steve Rogers"squad:steve-rogers (slugified, matches directory)

Fix: Added slugify() utility applied across:

  • triage.ts (core routing)
  • All template files (ralph-triage.js, squad-triage.yml, sync-squad-labels.yml)

Bug 2: Missing label pre-creation

gh issue edit --add-label fails with exit code 1 when the label doesn't exist. Error was swallowed silently.

Fix:

  • Added ensureTag() to PlatformAdapter interface + GitHub implementation
  • Watch startup now pre-creates all squad:{member} labels from team.md roster

Tests

  • 10 slugify tests (multi-word, parentheses, already-slugified, single-word, exempt names)
  • 2 triage label tests (verifying multi-word names produce correct labels)

Reproduction

# Before fix: team.md has "Steve Rogers", watch tries "squad:steve rogers" → fails
# After fix: watch uses "squad:steve-rogers" + creates label if missing

Verified

  • tsc --noEmit
  • npm run build
  • 41/41 tests pass ✅
  • Tested with real repo (tamirdresher/watch-triage-test): label created + assigned correctly

Closes #784

…ls (#784)

Bug 1: Watch triage derived labels from display names ('Steve Rogers' →
'squad:steve rogers') instead of directory slugs ('squad:steve-rogers').
Fix: Added shared slugify() utility, applied across triage.ts, templates,
and label sync workflows.

Bug 2: gh issue edit --add-label fails with exit code 1 when label doesn't
exist. Watch swallowed the error silently.
Fix: Added ensureTag() to PlatformAdapter + pre-create all squad:{member}
labels at watch startup.

12 new tests: slugify utility (10) + triage label verification (2).
All 41 tests pass. Build clean.

Closes #784

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 3, 2026 17:30
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 3, 2026

🛫 PR Readiness Check

⚠️ 2 item(s) to address before review

Status Check Details
Single commit 1 commit — clean history
Not in draft Ready for review
Branch up to date Up to date with dev
Copilot review No Copilot review yet — it may still be processing
Changeset present Changeset file found
Scope clean No .squad/ or docs/proposals/ files
No merge conflicts No merge conflicts
Copilot threads resolved No Copilot review threads
CI passing 11 check(s) still running

This check runs automatically on every push. Fix any ❌ items and push again.
See CONTRIBUTING.md and PR Requirements for details.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes squad watch triage getting stuck due to invalid/missing squad:{member} labels by standardizing label slugs and adding best-effort label pre-creation.

Changes:

  • Introduces an SDK slugify() utility and uses it for roster-derived labels (squad:steve-rogers vs squad:steve rogers).
  • Adds PlatformAdapter.ensureTag() plus a GitHub implementation, and calls it at watch startup to pre-create squad labels.
  • Updates triage-related templates/workflows to use the same slug logic; adds/extends tests for slug + roster parsing.

Reviewed changes

Copilot reviewed 23 out of 23 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
test/slugify.test.ts Adds unit tests for the new SDK slugify() utility.
test/ralph-triage.test.ts Extends roster parsing tests to assert slugified label output.
templates/workflows/sync-squad-labels.yml Slugifies member label names when creating/syncing labels (template copy).
templates/workflows/squad-triage.yml Slugifies displayed/assigned member labels (template copy).
templates/ralph-triage.js Slugifies roster-derived labels in the standalone triage script (template copy).
packages/squad-sdk/templates/workflows/sync-squad-labels.yml Slugifies member label names when creating/syncing labels (SDK template mirror).
packages/squad-sdk/templates/workflows/squad-triage.yml Slugifies displayed/assigned member labels (SDK template mirror).
packages/squad-sdk/templates/ralph-triage.js Slugifies roster-derived labels (SDK template mirror).
packages/squad-sdk/src/utils/slugify.ts Adds canonical slugify() implementation in the SDK.
packages/squad-sdk/src/ralph/triage.ts Uses slugify() for TeamMember.label generation and re-exports it.
packages/squad-sdk/src/platform/types.ts Extends PlatformAdapter with optional ensureTag() hook.
packages/squad-sdk/src/platform/github.ts Implements ensureTag() via gh label create --force.
packages/squad-cli/templates/workflows/sync-squad-labels.yml Slugifies member label names when creating/syncing labels (CLI template mirror).
packages/squad-cli/templates/workflows/squad-triage.yml Slugifies displayed/assigned member labels (CLI template mirror).
packages/squad-cli/templates/ralph-triage.js Slugifies roster-derived labels (CLI template mirror).
packages/squad-cli/src/cli/commands/watch/index.ts Pre-creates squad:{member} labels at watch startup using ensureTag().
index.js Slugifies roster-derived labels in the legacy/root watch subcommand implementation.
.squad-templates/workflows/sync-squad-labels.yml Slugifies member label names when creating/syncing labels (canonical templates).
.squad-templates/workflows/squad-triage.yml Slugifies displayed/assigned member labels (canonical templates).
.squad-templates/ralph-triage.js Slugifies roster-derived labels (canonical templates).
.github/workflows/sync-squad-labels.yml Slugifies member label names when creating/syncing labels (repo workflow).
.github/workflows/squad-triage.yml Slugifies displayed/assigned member labels (repo workflow).
.changeset/fix-triage-label-slug.md Changeset documenting the slug fix + label pre-creation behavior.

Comment on lines +135 to +139
try {
this.gh(args);
} catch {
// Label already exists or creation failed — continue either way
}
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ensureTag() swallows all gh label create failures. Since the command is run with --force, “already exists” shouldn’t be an error, so this catch is likely to hide real problems (e.g., missing repo scope / insufficient permissions), and watch will later fail to add labels with little diagnostic info. Consider only ignoring the specific “already exists” case (if needed) or rethrowing/logging unexpected errors so callers can surface them (at least in verbose mode).

Suggested change
try {
this.gh(args);
} catch {
// Label already exists or creation failed — continue either way
}
this.gh(args);

Copilot uses AI. Check for mistakes.
Comment on lines +667 to +675
for (const member of roster) {
try {
await adapter.ensureTag(member.label, { color: 'd4c5f9', description: `Squad triage: ${member.name}` });
} catch { /* best-effort — continue if label creation fails */ }
}
try {
await adapter.ensureTag('squad:copilot', { color: 'd4c5f9', description: 'Squad triage: Copilot coding agent' });
} catch { /* best-effort */ }
console.log(`${DIM}Labels: ensured ${roster.length + 1} squad labels exist${RESET}`);
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The startup message says labels were “ensured … exist”, but label creation is best-effort and both ensureTag() and this caller swallow errors. This can produce a misleading success message even when none of the labels were created. Consider tracking successes and reporting “attempted to ensure” (or only printing the message when all creations succeed / when verbose is enabled).

Suggested change
for (const member of roster) {
try {
await adapter.ensureTag(member.label, { color: 'd4c5f9', description: `Squad triage: ${member.name}` });
} catch { /* best-effort — continue if label creation fails */ }
}
try {
await adapter.ensureTag('squad:copilot', { color: 'd4c5f9', description: 'Squad triage: Copilot coding agent' });
} catch { /* best-effort */ }
console.log(`${DIM}Labels: ensured ${roster.length + 1} squad labels exist${RESET}`);
const totalSquadLabels = roster.length + 1;
let ensuredSquadLabels = 0;
for (const member of roster) {
try {
await adapter.ensureTag(member.label, { color: 'd4c5f9', description: `Squad triage: ${member.name}` });
ensuredSquadLabels += 1;
} catch { /* best-effort — continue if label creation fails */ }
}
try {
await adapter.ensureTag('squad:copilot', { color: 'd4c5f9', description: 'Squad triage: Copilot coding agent' });
ensuredSquadLabels += 1;
} catch { /* best-effort */ }
if (ensuredSquadLabels === totalSquadLabels) {
console.log(`${DIM}Labels: ensured ${totalSquadLabels} squad labels exist${RESET}`);
} else {
console.log(`${DIM}Labels: attempted to ensure ${totalSquadLabels} squad labels exist (${ensuredSquadLabels} succeeded)${RESET}`);
}

Copilot uses AI. Check for mistakes.
Comment on lines +4 to 5
export { slugify } from '../utils/slugify.js';

Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This module now re-exports slugify from utils/slugify, which expands the public surface of ralph/triage beyond triage concerns. If the intent is to make slugify generally available, consider exporting it from a more appropriate public location (e.g., a utils/barrel) rather than from the triage module to avoid confusing API organization.

Suggested change
export { slugify } from '../utils/slugify.js';

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +15
/**
* Convert a display name to a URL/label-safe slug.
*
* "Steve Rogers" → "steve-rogers"
* "Tony Stark (Iron Man)" → "tony-stark-iron-man"
* "Doctor Strange (Stephen Strange)" → "doctor-strange-stephen-strange"
* "Thor" → "thor"
* "already-slugified" → "already-slugified"
*/
export function slugify(name: string): string {
return name
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-|-$/g, '');
}
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are already multiple identical private slugify() implementations in the SDK (e.g., under src/marketplace/*). Introducing another canonical implementation is fine, but it increases duplication unless the existing ones are migrated to import this utility. Consider either reusing an existing shared slugify helper (if one is intended) or planning a follow-up to deduplicate so behavior can’t drift across features.

Copilot uses AI. Check for mistakes.
Comment on lines 121 to 125
members.push({
name,
role,
label: `squad:${name.toLowerCase()}`,
label: `squad:${slugify(name)}`,
});
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parseRoster() now derives label via slugify(name), but the TeamMember.label field doc comment still describes it as squad:{lowercase name}. Since this function defines the canonical label format, the type-level documentation should be updated to match the slugified behavior to avoid misleading SDK consumers.

Copilot uses AI. Check for mistakes.
@tamirdresher tamirdresher requested a review from diberry April 3, 2026 18:09
Copy link
Copy Markdown
Collaborator

@diberry diberry left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Review Summary

Verdict: ✅ Approve

What's Good

  • Real bug fix — \squad:steve rogers\ (space) → \squad:steve-rogers\ (slugified). Root cause of watch triage looping forever on 'Untriaged: 1'.
  • Thorough coverage — Fixed slug in ALL locations: SDK triage.ts, CLI watch/index.ts, 3 template copies of ralph-triage.js, 3 workflow yml copies (triage + sync-labels), plus live .github/workflows/\ copies. No orphan instances.
  • \�nsureTag()\ well-designed — Optional on interface (\�nsureTag?), uses \gh label create --force\ (idempotent), best-effort with swallowed errors. Pre-creates at watch startup.
  • Canonical \slugify()\ utility — Single source of truth in \packages/squad-sdk/src/utils/slugify.ts. 10 tests covering multi-word, parentheses, already-slugified, single-word.
  • Changeset included — Patch for both packages. Tested on real repo.

Suggestions (non-blocking)

  1. Add // Keep in sync with slugify.ts\ comments to the 6+ inline copies in workflows/templates
  2. Consider one \console.warn\ in the inner \�nsureTag\ catch for easier debugging
  3. Add \slugify('@copilot') === 'copilot'\ test case to document the @ stripping behavior

@tamirdresher tamirdresher merged commit 2129ad7 into dev Apr 3, 2026
16 checks passed
diberry pushed a commit that referenced this pull request Apr 3, 2026
…ls (#784) (#785)

Bug 1: Watch triage derived labels from display names ('Steve Rogers' →
'squad:steve rogers') instead of directory slugs ('squad:steve-rogers').
Fix: Added shared slugify() utility, applied across triage.ts, templates,
and label sync workflows.

Bug 2: gh issue edit --add-label fails with exit code 1 when label doesn't
exist. Watch swallowed the error silently.
Fix: Added ensureTag() to PlatformAdapter + pre-create all squad:{member}
labels at watch startup.

12 new tests: slugify utility (10) + triage label verification (2).
All 41 tests pass. Build clean.

Closes #784

Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] squad watch triage fails silently — wrong label slug + missing label pre-creation

3 participants