feat(pool): label-aware instance selection (match_labels on dispatch) #109
No reviewers
Labels
No labels
area:agents
area:dashboard
area:database
area:design
area:design-review
area:flows
area:infra
area:meta
area:security
area:sessions
area:webhook
area:workdir
security
type:bug
type:chore
type:meta
type:user-story
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
charles/claude-hooks!109
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "dev/50"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
dispatchByTypewithopts.labels?: string[]; selection order: match (largest intersection) → catch-all (emptymatch_labels) → last-resort all + warning logpayload.issue.labels; PR events (review, changes-requested, rebase, post-merge rebase) parseCloses #Nfrom the PR body and fetch the linked issue's labels via newgetIssueinforgejo-api.tsmatch_labels?: string[]toWorkerConfigand thread it throughregisterWorkerso the pool reads per-instance label preference at dispatch timedispatchByType — label-aware selectiontests (a–e per acceptance criteria)Test plan
bun test— all 357 tests pass including the 5 new label-aware dispatch testsbunx tsc --noEmit— no type errorsbunx biome check src/— no lint/format issuesreviewer-securityinstance withmatch_labels: ["security"]in SQLite, dispatch a PR whose linked issue has thesecuritylabel — verify the log showsmatch: [security] ∩ [security,...]and picksreviewer-securityCloses #50
🤖 Generated with Claude Code
Extend `dispatchByType` with `opts.labels?: string[]` for label-aware pool scheduling. When labels are supplied the dispatcher: 1. Prefers instances whose `match_labels` intersect `opts.labels` (largest intersection wins; tie-break by idle-first / least-loaded). 2. Falls back to catch-all instances (empty `match_labels`). 3. Last resort: all instances + warning log. Wire labels into every webhook dispatch path: - Issue events: extract label names from `payload.issue.labels`. - PR events (review, changes-requested, rebase, post-merge rebase): fetch the linked issue via `Closes #N` in the PR body and use its labels (new `getIssue` in `forgejo-api.ts`). Add `match_labels?: string[]` to `WorkerConfig` and pass it through `registerWorker` at startup so the pool can read the per-instance label preference without touching the DB. Adds five new `dispatchByType — label-aware selection` tests covering match, largest-intersection, catch-all fallback, last-resort warning, and no-labels baseline (A2 behaviour unchanged). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>Review: APPROVED ✅
CI green (run #1655, success, 2m48s). All acceptance criteria from issue #50 met. Code is correct. No bugs or safety issues found.
Acceptance criteria check
dispatchByTypeacceptsopts.labels?: string[]opts.labelsabsent/empty → A2 behavior unchangedCloses #Npayload.issue.labelsmatch_labels: []/nullinstances are catch-allsCode quality notes (no changes required)
pool.ts—intersectionSizecorrectly measures overlap; max-score tier +pickA2within it honours both specificity and load-balancing. A2 baseline path runs across all candidates regardless ofmatch_labels, which is correct per spec.forgejo-api.ts—getIssueis minimal and consistent with existing helpers. Errors degrade gracefully to[]labels → catch-all dispatch.webhook-handlers.ts— All five dispatch paths (issue-assigned, issue-labeled, review-requested, changes-requested/rebase, post-merge-rebase) correctly thread labels through todispatchByType.parseClosesIssuecoverscloses|fixes|resolvescase-insensitively; documented choice to parse PR body over a non-existent Forgejo API endpoint is correct.worker.ts/main.ts—match_labels?: string[]added toWorkerConfigwith clear JSDoc;registerWorkerpasses it through from SQLite resolved agents.Tests — Cases (a)–(e) map 1:1 to acceptance criteria. Case (d) spy on
console.logcorrectly asserts the no-catch-all warning.