Merge dev to main: Linear router multi-project-per-team fix#1334
Merged
Conversation
Co-authored-by: Cascade Bot <bot@cascade.dev>
…t team
User reported MNG-638 (Linear) wasn't triggering the implementation agent
despite multiple moves to Todo and `cascade-ready` label applications.
Investigation found:
1. Linear webhooks ARE arriving (`webhooklogs list --source linear`) but
every one is `processed=False decisionReason="Event unparseable or
not processable"`.
2. Loki shows the real reason — `LinearRouterAdapter: dropping event
outside project scope` — fires ~20 times on MNG-638's UUID. The
webhooklogs UI hides the specific drop reason because parseWebhook()
returns null and the outer wrapper emits the generic "unparseable"
message.
3. The actual bug: two cascade projects share one Linear team after
`cascade` was migrated from Trello → Linear:
- `cascade` → Linear team 310c41fe-..., scoped to Linear Project 83a0f22b-...
- `ucho` → same team, scoped to Linear Project 7108c72e-...
The router used `config.projects.find((p) => p.linear?.teamId === teamId)`
which always returned the first match by team. The follow-up
matchesConfiguredProjectScope() then dropped events whose issue
belonged to the OTHER cascade project's Linear scope. MNG-638 is in
cascade's Linear Project, but the .find() picked ucho first → scope
mismatch → drop. Latent until the second cascade project landed on
the same team this morning; now a deterministic loss for everything
in cascade's Linear Project.
Fix: replace "find first by teamId" with "best match by teamId + Linear
Project scope".
- Collect ALL candidates that share the teamId via `.filter()`.
- Extract issue's Linear Project ID BEFORE selecting a candidate
(`resolveIssueProjectId` helper — pulls from data.projectId /
data.project.id, or fetches via API for Comment events without inline
context).
- Pick the candidate with deterministic fallback:
1. Strong match: candidate whose `linear.projectId` equals issue's project.
2. Catch-all: candidate with NO `linear.projectId` configured.
3. Otherwise: drop with the existing info-level log (now augmented
with the full candidates list for diagnostics — "no candidate
matches issue project" vs "issue has no project").
Delete `matchesConfiguredProjectScope` — its job is now done inline by
the new selection logic. `fetchIssueProjectId` stays (called by the
helper). When `candidates.length === 1` the new logic degrades to the
old behavior, so single-project-per-team setups continue working.
Tests:
- `tests/unit/router/adapters/linear.test.ts`: updated the existing scope-
filter assertions to match the new log shape (reason field + candidates
list), added a `parseWebhook — multi-cascade-project-per-team` describe
block with 7 scenarios:
* routes to the project matching the issue's Linear scope (cascade)
* routes to the other project when the issue belongs to it (ucho)
* drops with candidates list when no candidate subscribes
* drops with `'issue has no project'` reason when issue is unscoped
and all candidates are scoped
* falls back to unscoped catch-all candidate when no scoped match
* prefers the scoped match over an unscoped catch-all
* Comment event uses API fallback via the first candidate's creds
All 45 LinearRouterAdapter tests pass.
JIRA and Trello router adapters use their own unique discriminator
(project key / board ID) per cascade project so they don't share the
same multi-project-per-discriminator vulnerability.
Verification: full suite (9280 tests) clean. After deploy, re-moving
MNG-638 to Todo should route to `cascade` (issue's projectId `83a0f22b-...`
matches cascade's scope) and fire the implementation agent.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Cascade Bot <bot@cascade.dev>
…multi-project-per-team fix(router): route Linear webhooks by team + issue's project, not just team
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Test plan
🤖 Generated with Claude Code