Release: dev → main (Linear integration + wizard/trigger fixes)#1122
Merged
zbigniewsobiecki merged 57 commits intomainfrom Apr 16, 2026
Merged
Release: dev → main (Linear integration + wizard/trigger fixes)#1122zbigniewsobiecki merged 57 commits intomainfrom
zbigniewsobiecki merged 57 commits intomainfrom
Conversation
Add a hub-and-spoke architecture documentation suite covering all backend subsystems (excluding tests and dashboard web frontend). The hub document provides a system overview with Mermaid diagrams and links to 10 deep-dive documents covering services, webhook pipeline, triggers, agents, engines, integrations, gadgets, config/credentials, database, and resilience. Also adds 36 unit tests validating doc structure and cross-references, and removes a stale biome suppression comment in project-harness-form.tsx. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(crypto): validate CREDENTIAL_MASTER_KEY format at startup * fix(deps): resolve high-severity lodash-es vulnerability via overrides Add npm overrides to force lodash-es>=4.18.1, resolving GHSA-r5fr-rjxr-66jc (Code Injection via _.template) and GHSA-f23m-r3pf-42rh (Prototype Pollution) which affected transitive deps chevrotain/js-toml/llmist. Also updates brace-expansion to 2.0.3 to resolve moderate GHSA-f886-m6hf-6m8v. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Cascade Bot <bot@cascade.dev> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(security): invalidate user sessions on password change * fix(deps): add overrides to resolve high-severity npm audit vulnerabilities Add npm overrides for lodash@^4.18.1, lodash-es@^4.18.1, and brace-expansion@^2.0.3 to address high-severity CVEs in transitive dependencies (archiver → lodash, @llmist/cli → chevrotain → lodash-es). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Cascade Bot <bot@cascade.dev> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: zbigniew sobiecki <zbigniew@sobiecki.name>
…1072) Co-authored-by: Cascade Bot <bot@cascade.dev>
Co-authored-by: Cascade Bot <bot@cascade.dev>
…ompts (#1074) Co-authored-by: Cascade Bot <bot@cascade.dev>
Co-authored-by: Cascade Bot <bot@cascade.dev>
…ges (#1076) Co-authored-by: Cascade Bot <bot@cascade.dev>
…te legacy functions (#1077) Co-authored-by: Cascade Bot <bot@cascade.dev>
…grations/bootstrap.ts (#1078) * fix(pm): consolidate PM registration to single canonical path in integrations/bootstrap.ts * fix(test): add bootstrap import to pm-provider-switching integration test After removing the side-effect registration from src/pm/index.ts, the pm-provider-switching integration test no longer had pmRegistry populated, causing the pmRegistry assertions and createPMProvider calls to fail. Add the canonical bootstrap import (matching integration-validation.test.ts). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Cascade Bot <bot@cascade.dev> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…0.2.91 (#1079) Co-authored-by: Cascade Bot <bot@cascade.dev>
…nd (#1080) Co-authored-by: Cascade Bot <bot@cascade.dev>
…oncurrency (#1081) * refactor(triggers): extract shared webhook utilities and add Sentry concurrency * fix(triggers): move startWatchdog inside concurrency callback and fix README - Move startWatchdog() inside the execute closure (GitHub) and inside the concurrency callback (Sentry) so it only fires when an agent actually runs. Previously the watchdog timer started before withAgentTypeConcurrency, so a concurrency-blocked job would tick a timer that could fire process.exit(1) after the container finished its work. - Fix Worker-Side Handler Comparison table: GitHub now uses withAgentTypeConcurrency (not checkAgentTypeConcurrency directly). - Fix Shared Utilities table: remove PM from the "Used By" column for concurrency.ts since src/pm/webhook-handler.ts still uses raw checkAgentTypeConcurrency. - Update flow diagrams to show startWatchdog nested inside withAgentTypeConcurrency. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(triggers): fix inaccurate "Used By" entries and module comment - trigger-resolution.ts: correct "Used By" to "Sentry (GitHub and PM use inline logic)" — GitHub handler uses inline if/else, not resolveTriggerResult - pm-ack.ts: correct "Used By" to "GitHub worker handler" — the router adapter has its own local postPMAck and does not import the shared utility - pm-ack.ts module comment: remove false claim about router-side usage; add note clarifying the router adapter has its own local function - GitHub webhook flow diagram: replace resolveTriggerResult with accurate inline dispatch description Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Cascade Bot <bot@cascade.dev> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…e-driven alternatives (#1082) Co-authored-by: Cascade Bot <bot@cascade.dev>
…recated alternatives (#1083) Co-authored-by: Cascade Bot <bot@cascade.dev>
…to capability strings (#1084) Co-authored-by: Cascade Bot <bot@cascade.dev>
…rm.tsx (#1085) Co-authored-by: Cascade Bot <bot@cascade.dev>
…ocedure (#1086) Co-authored-by: Cascade Bot <bot@cascade.dev>
* chore: remove Squint codebase intelligence support * chore: remove leftover SQUINT_DB_PATH entries from engine env allowlists Remove dead `SQUINT_DB_PATH` entries from the engine environment allowlists in claude-code, codex, and opencode backends (both env.ts and index.ts), and remove the misleading documentation example in docs/adding-engines.md. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Cascade Bot <bot@cascade.dev> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(pm): add Linear PM provider adapter and integration * fix(linear): address review feedback on Linear PM provider adapter - Register LinearIntegration in bootstrap.ts following Trello/JIRA pattern - Fix addChecklistItem to pass parentId to createIssue() for proper sub-issue relationship - Add parentId to LinearCreateIssueInput type in linear/types.ts - Fix isSelfAuthored to use withLinearCredentials() with project credentials - Remove dead code from sendReaction (no-op with dead extraction logic) - Import canonical LinearConfig from pm/config.ts instead of redeclaring local type - Fix getPromptTerminology() to return issue/Linear for Linear projects - Fix getListIds() to read Linear statuses config for prompt context - Add Linear config to RouterProjectConfig and loadProjectConfig() Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Cascade Bot <bot@cascade.dev> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
#1097) Co-authored-by: Cascade Bot <bot@cascade.dev>
Security patches for two advisories blocked on Dependabot: - vite 6.4.1 → 6.4.2: path traversal in optimize deps sourcemap handler, server.fs check for env transport (vitejs/vite#22161, #22159) - picomatch 4.0.3 → 4.0.4: CVE-2026-33671, CVE-2026-33672 Replaces #1088 and #1058, which were stuck on CI because Dependabot's lockfile regeneration produced a divergent lockfile vs. dev (dropped @trpc/server and react-is resolved entries, added platform-specific tailwindcss-oxide-wasm32-wasi nested entries). Rather than iterate on @dependabot recreate, bundled both bumps into a single manual PR with a lockfile regenerated from dev's current state. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(linear): add Linear webhook ingestion in router * fix(linear): implement isSelfAuthored to prevent infinite comment loop Add resolveLinearBotUserId() using the Linear GraphQL viewer query, mirroring the JIRA bot identity pattern. isSelfAuthored() now compares the comment payload's userId against the bot's user ID so bot-posted ack comments are filtered before trigger dispatch, preventing an infinite create/Comment webhook loop when triggers match comment creation events. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Cascade Bot <bot@cascade.dev> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(linear): add Linear worker dispatch and trigger handlers * fix(linear): address review feedback on duplication and dead code - Extract STATUS_TO_AGENT to src/triggers/shared/status-to-agent.ts and re-export from jira/types.ts and linear/types.ts to eliminate duplication - Replace inline Linear viewer query in comment-mention.ts with the existing resolveLinearBotUserId() from bot-identity-resolvers.ts (cached, no raw fetch) - Clean up hasMention(): remove dead code branch and fix inconsistent JSDoc Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Cascade Bot <bot@cascade.dev> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Cascade Bot <bot@cascade.dev>
…ntegration (#1102) Co-authored-by: Cascade Bot <bot@cascade.dev>
…#1103) Co-authored-by: Cascade Bot <bot@cascade.dev>
…overy methods (#1104) Co-authored-by: Cascade Bot <bot@cascade.dev>
Co-authored-by: Cascade Bot <bot@cascade.dev>
…vents (#1106) Co-authored-by: Cascade Bot <bot@cascade.dev>
…1107) Co-authored-by: Cascade Bot <bot@cascade.dev>
… tool (#1108) Co-authored-by: Cascade Bot <bot@cascade.dev>
…ponents (#1110) Co-authored-by: Cascade Bot <bot@cascade.dev>
…mpact diffs, add SKIPPED FILES contract (#1111) Spec [001 (pr-review-correctness)](docs/specs/001-pr-review-correctness.md.done) implemented end-to-end. Root cause of the PR #1092 incident: silent `git checkout` failure on external-fork PRs + a 25k-token full-file pre-fetch that fit only 20 of 129 changed files into the agent's view. This PR fixes both, plus bundles in-flight Linear/PM-wizard work and clears 3 pre-existing biome cognitive-complexity warnings. Highlights: - Plan 001/1: `refs/pull/N/head` checkout (works for forks), fail-loud setup, HEAD-SHA verification, full pagination of 7 GitHub list endpoints. - Plan 001/2: Compact per-file diffs replace full-file pre-fetch; structured self-documenting `SKIPPED FILES` injection benefits all 5 agents that consume `prContext`. - Bundled: pm-wizard provider-switch credential cleanup; `web/tsconfig.json` extends include for shared `getCredentialRoles`. - Cleanup: `users.ts`/`webhooks.ts`/`pm-wizard.tsx` complexity warnings cleared via focused helpers. Tests: 7538/7538 unit pass (+54 new), typecheck/lint/build clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
#1112) * fix(linear): remove Bearer prefix from API key auth header + fix biome config **Bug fix:** Linear personal API keys (lin_api_*) must be sent as bare Authorization tokens — not Bearer tokens. Bearer is for OAuth access tokens only. Every outbound Linear API call (getMe, getTeams, createIssue, etc.) was broken for any project using a personal key. Reproducer: curl -H "Authorization: Bearer lin_api_..." returned "Remove the Bearer prefix from the Authorization header." **Also included:** - Add logger.error in wrapIntegrationCall so integration failures are visible in server logs (previously swallowed silently) - Add getMe unit tests (the method that was failing in the wizard) - Fix biome.json: schema ref was 2.4.10 but installed biome is 1.9.4; also renamed assist→organizeImports, includes→ignore (1.9.x keys) - Run biome check --write to apply all import-sort and style fixes across the entire codebase (pre-existing issues, now unblocked) - Add biome override to keep noDelete off for src/ and tests/ — delete process.env.X is semantically required (= undefined coerces to the string "undefined" in Node.js process.env) - Remove stale biome-ignore comment in llm-call-list.tsx Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(lint): align biome.json with installed version 2.4.10 Local node_modules had biome 1.9.4 (stale from before #1075), so the previous commit wrote a 1.9.4 config that broke CI (which installs fresh 2.4.10). After running npm install locally, re-wrote config for 2.4.10: - Restore assist/files.includes keys (valid 2.x API) - Keep noDelete: off override (src/ + tests/) — delete process.env.X is semantically required; = undefined coerces to "undefined" string - Add noArrayIndexKey suppression back to llm-call-list (2.4.10 triggers the rule even for compound keys; suppression is warranted — read-only list) - Re-apply biome 2.4.10 import sort across modified files Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs(plans): add spec 002 and plans; lock plan 002/1
Spec 002 (linear-webhook-setup-ux) introduces the Linear wizard UX
improvements and save-path fix. Plan 1 locked for execution.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(dashboard): structured error logging for tRPC + Hono
Adds src/api/errorLogging.ts with formatters that surface pg driver
fields (code, detail, constraint, table) onto server-side logs for
both Hono app.onError and the tRPC errorFormatter. Client responses
for INTERNAL_SERVER_ERROR now carry a generic message; real details
remain in cascade-dashboard-* stdout for operators to grep.
plan 002/1 task 1.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(db): allow provider=linear under category=pm in integration check constraint
The chk_integration_category_provider check constraint (from
0047_add_alerting_integration.sql) only permitted trello/jira for
pm-category integrations. Linear support was introduced without a
matching constraint update, so every projects.integrations.upsert
for category=pm + provider=linear failed with SQLSTATE 23514 and
surfaced as HTTP 500 from the dashboard Linear wizard.
Migration 0049 drops and re-creates the constraint with linear added
to the pm branch. Forward-only, no data migration required.
Reproduced with two new integration tests against the test DB.
plan 002/1 tasks 2-3 (diagnose + fix).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(api): assert plaintext credentials never leak into server logs
Integration tests that spy on console.{log,error,warn} around
writeProjectCredential, upsertProjectIntegration (happy + FK-violation
paths), and the formatTRPCErrorLog formatter. Asserts the sentinel
credential value never appears in captured output, while confirming
the real PG error (SQLSTATE + constraint name) IS present on the
failure path — proving diagnosability without leakage.
plan 002/1 task 4.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs(plans): plan 002/1 (save-path-fix) done — unblock Linear wizard Save
All 11 per-plan ACs pass. Spec ACs #5-8 delivered:
- Linear upsert succeeds on fresh projects and re-configuration
- DB error diagnostics (SQLSTATE, constraint, detail) surface in server logs
- tRPC clients receive generic 'Internal server error' for unexpected throws
- Plaintext credentials never leak into captured stdout/stderr
Caveat: CLAUDE.md addition deferred to avoid conflicting with the
user's in-progress rewrite of that file. Doc impact met via CHANGELOG.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs(plans): update plan 002/1 progress checkboxes to done
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs(plans): lock plan 002/2 + record testing-approach divergence
Plan 2 originally assumed @testing-library/react in web/; project has no
such infrastructure and the web/ package has no test script. Plan edited
in place to:
- use react-dom/server.renderToStaticMarkup (available; no new deps)
- test under the existing root unit-core project
- drop interactive-mutation tests (would need jsdom); ProjectSecretField
internals are out-of-scope per spec non-goal
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs(plans): plan 002/2 frontmatter status -> wip
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(dashboard): linear wizard UX — accurate events list and inline signing secret
Plan 002/2 (wizard-webhooks-step).
Linear PM wizard Webhooks step now:
- lists the three event families CASCADE consumes (Issues, Comments,
Issue Labels), each with a one-line rationale tracing to
src/triggers/linear/
- renders a ProjectSecretField bound to LINEAR_WEBHOOK_SECRET directly
beneath the webhook URL, matching the Sentry alerting tab pattern
- drops the 'store as LINEAR_WEBHOOK_SECRET in project credentials'
trailing bullet (replaced by the inline input)
PMWizard threads projectId + the masked LINEAR_WEBHOOK_SECRET credential
meta from projects.credentials.list through WebhookStep, so the field
reflects its own stored value on mount and stays in sync with the
Credentials tab.
Tests: 17 new SSR tests using react-dom/server — no React testing
library required. Interactive mutation firing is out of scope (would
need jsdom); ProjectSecretField itself is untested today under the
same constraint and is spec non-goal to modify.
vitest.config.ts gains @/components, @/lib, @/hooks aliases pointing at
web/src/ for test-time resolution.
src/integrations/README.md Linear section defers to the dashboard
wizard as the authoritative setup source.
Plan 2 closes out spec 002.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs(claude): streamline CLAUDE.md to essentials
Compacts the project's CLAUDE.md from ~820 to ~163 lines by dropping
dated setup minutiae, duplicated process docs, and legacy sections.
Keeps the load-bearing context: architecture, PR checkout gotcha,
testing commands, Zod policy, migrations, GitHub dual-persona rules,
trigger system, engines, environment, and git hooks.
No code changes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs(plans): add spec 003 + plan; lock plan 003/1 Spec 003 introduces PM status mapping parity across Linear and JIRA. Plan 1 (status-parity) locked for execution. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs(plans): plan 003/1 frontmatter status -> wip Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(pm): status mapping parity across Linear and JIRA Plan 003/1 (status-parity). Linear PM wizard Field Mapping step now exposes all 8 CASCADE stages (backlog, splitting, planning, todo, inProgress, inReview, done, merged) in lifecycle order instead of only 4. Linear operators can now map workflow states to splitting/planning/todo/merged and have the corresponding agents dispatch on issue transitions. JIRA's silent-drop bug in resolveLifecycleConfig fixed: splitting/ planning/todo mappings the JIRA wizard already accepted now surface through to PMLifecycleManager and GitHub PR triggers. No operator action required. Canonical ProjectPMConfig.statuses widens to declare the full 9-stage vocabulary (including debug, reserved for future trigger), so providers can no longer silently drift from the trigger layer. Existing Linear integrations upgrade in place: new slots render as 'not set' on next wizard visit. No migration. Tests: 9 new unit tests (type shape + Linear + JIRA integration + SSR wizard). Integration coverage for spec ACs #8/#9 provided by existing linear-status-changed and jira-status-changed trigger handler tests — discovered during Phase 4 that handlers read provider-specific config directly (not resolveLifecycleConfig), so dispatch was never blocked for handlers; the drop was in downstream PMLifecycleManager callers. Totals: 7597 unit + 522 integration all green. Lint + typecheck clean. Closes spec 003. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs(specs): spec 003 (linear-status-mapping-parity) done — all plans complete Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs(plans): add spec 004 + plan; lock plan 004/1
Spec 004 targets the credential role-resolution ambiguity that breaks
Linear wizard reopens and silently skips Linear webhook signature
verification in the router.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs(plans): plan 004/1 frontmatter status -> wip
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(config): disambiguate credential role resolution by provider
Plan 004/1 (disambiguate-role-resolver).
The credential-resolution helper iterated all providers in a category
looking for a role-name match and returned the first hit. Because the
built-in PM registration order is trello → jira → linear, this caused:
1. Dashboard: Linear wizard reopens demanded a re-typed API key every
time. ('pm', 'api_key') resolved to TRELLO_API_KEY on a Linear-only
project, which isn't configured, so 'Linear credentials not
configured' surfaced in the Board/Project Selection step.
2. Router (production): Linear webhook signature verification silently
returned the JIRA webhook secret (or null on Linear-only projects),
causing signatures to silently skip verification.
Fix:
- roleToEnvVarKey(category, role) -> roleToEnvVarKey(category, provider, role)
- getIntegrationCredential[OrNull] now take (projectId, category,
provider, role) — compiler rejects callers that omit provider.
- 42 call sites updated across 13 src/ files to pass the explicit
provider.
- *ByProject dashboard endpoints look up project_integrations.provider
first; throw NOT_FOUND with distinguishable message on missing row.
- resolveWebhookSecret branches pass the explicit provider per branch.
New tests:
- tests/unit/config/role-resolver.test.ts (9 tests)
- tests/unit/router/resolveWebhookSecret.test.ts (6 tests)
- integrationsDiscovery tests augmented with default provider mock.
Totals: 7612 unit + 522 integration all green. Lint + typecheck clean.
Closes spec 004.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs(specs): spec 004 (credential-role-provider-disambiguation) done — all plans complete
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Captures the spec, three execution plans, and coverage map for adding an optional Linear Project scope to the Linear PM integration. All three plans are .done; the code landing in the next two commits implements them. See docs/specs/005-linear-project-scope.md.done for the feature- level spec. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…very)
Delivers the backend half of the optional Linear Project scope feature
(see docs/specs/005-linear-project-scope.md.done).
Config + outbound operations:
- `LinearConfig.projectId?: string` — new optional field on the PM
config JSONB. Absence preserves existing full-team behavior.
- `linearClient.listIssues()` accepts `projectId` and translates it to
a `project.id.eq` GraphQL filter.
- `linearClient.createIssue()` passes `projectId` through to the
mutation input via `LinearCreateIssueInput.projectId`.
- `LinearPMProvider.listWorkItems()`, `createWorkItem()`, and
`addChecklistItem()` propagate `config.projectId` to every outbound
call, so sub-issues stay in scope.
Router-side webhook filter:
- `RouterProjectConfig.linear.projectId?` + `loadProjectConfig()` copy
the field from `LinearConfig` into the router-side projection.
- `LinearRouterAdapter.parseWebhook()` drops Issue, Comment, and
IssueLabel events whose issue's projectId differs from the configured
one (or has no project), returning null so no downstream step fires.
A structured `logger.info('LinearRouterAdapter: dropping event
outside project scope', { reason, configuredProjectId,
issueProjectId, issueId, teamId, projectId, eventType })` entry
records the drop so operators can debug "why isn't CASCADE picking
up my issue?" — info-level is a deliberate choice.
Wizard discovery endpoint:
- `linearClient.getTeamProjects(teamId, first = 250)` — new GraphQL
method + `LinearProject` type. Pagination cap prevents truncation
for large teams.
- `linearProjects` + `linearProjectsByProject` tRPC procedures mirror
the `linearTeams` / `linearTeamsByProject` pattern so the wizard UI
can populate a project dropdown.
Cross-team intersection is enforced: sibling-team issues in a shared
Linear project are dropped by the existing teamId lookup before the
new filter runs. Integrations without `projectId` set see zero
behavior change; no migration.
Tests: 33 new unit tests across `tests/unit/linear/client.test.ts`
(new file, 9 tests), `tests/unit/pm/linear-adapter.test.ts` (new file,
7 tests), `tests/unit/pm/linear-integration.test.ts` (4 new tests),
`tests/unit/router/adapters/linear.test.ts` (8 tests for the scope
filter), and `tests/unit/api/routers/integrationsDiscovery.test.ts`
(8 tests for the new procedures). `unit-core` project now includes
`tests/unit/linear/**`.
Spec: docs/specs/005-linear-project-scope.md.done
Plans: 005/1 (scope-config-and-outbound) + 005/2 (webhook-scope-
filter-and-discovery).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Closes spec 005 by shipping the operator-facing half. The backend (previous commit) now has a UI driver that writes `projectId` into the Linear integration config. Wizard state: - `pm-wizard-state.ts` — `linearProjectId` + `linearProjects` fields, `SET_LINEAR_PROJECTS` + `SET_LINEAR_PROJECT_ID` actions, and a reset-on-team-change hook (a new team invalidates the project list). - `buildEditState()` hydrates `linearProjectId` from a saved config so reopening the wizard for an edited project pre-selects the scope. - `buildLinearIntegrationConfig(state)` — new pure save-payload builder. Keeps the save mutation thin and gives the payload shape a direct unit-test surface without a React runtime. Discovery hook: - `useLinearDiscovery()` — adds `linearProjectsMutation` mirroring the existing `linearDetailsMutation` pattern (byProject + raw-creds variants; fires after team selection and on editing-mount when a team is already stored). UI: - `LinearTeamStep` — renders a SearchableSelect for "Linear Project (optional)" under the Team selector, but only when a team is selected. Native placeholder `<option value="">` doubles as the clear control. Helper copy explicitly marks the field optional and names the fallback behavior. - `LinearWebhookInfoPanel` — adds a one-paragraph callout clarifying that project-scope filtering happens on CASCADE's side — Linear webhook config stays team-scoped and unchanged. Pre-empts the predictable support question. - Save payload now includes `projectId` exactly when the selector has a value; omitted when empty. Clearing persists as "no project scope". Docs: - `src/integrations/README.md` — Linear operator-setup paragraph now mentions the optional project scope and where it lives in the wizard. - `CHANGELOG.md` — single operator-facing Unreleased entry covering the whole feature. Tests: 21 new unit tests across pm-wizard-state (reducer + hydration + save-payload builder), linear-team-step (new SSR component tests covering render gating, options population, edit-mode pre-selection, clear behavior, helper copy), and linear-webhook-info-panel (regression + new callout copy). AC #15 of plan 3 (manual end-to-end smoke test with a live Linear workspace + webhook delivery) is an operator post-merge verification step — not executable in CI. Spec: docs/specs/005-linear-project-scope.md.done Plan: 005/3 (wizard-ui). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…oject-scope feat(linear): optional Linear Project scope for PM integration
Linear webhooks deliver workflow-state UUIDs in data.stateId, but the PM wizard's status-mapping dropdown was saving display names as the option value. The status-changed trigger does strict-equality matching on the saved value, so every Linear status transition since the integration landed has been silently no-oping. JIRA gets away with names because JIRA webhooks deliver names; Linear does not. Changed the option value to s.id (s.name remains as the label so the dropdown still reads naturally). Added a regression test asserting every state ID appears as an option value and no state name does, plus a WHY comment to anchor the contract for future maintainers. Operator action required after deploy: re-run the Status Mappings wizard step on existing Linear-backed projects. Each saved name will no longer match a value, so dropdowns will appear empty — re-pick from the dropdown and save. New mappings persist as UUIDs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…uuid-mapping fix(linear): store state IDs in status mappings
Linear jobs were not handled by extractProjectIdFromJob, so the router returned null and skipped credential resolution. Workers spawned with no credentials, then fell back to the DB and died with "Credential is encrypted but CREDENTIAL_MASTER_KEY is not set" because workers intentionally don't receive the master key. Linear jobs already carry projectId on their payload (set by LinearRouterAdapter.buildJob), so the fix is a single branch in the projectId extractor. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…credentials fix(linear): pass projectId for credential resolution to worker
Linear personal API keys (lin_api_*) are sent bare in the Authorization header — the `Bearer` prefix is OAuth-only, and using it with personal keys triggers HTTP 400. The router's two Linear API call sites (platformClients/linear.ts, bot-identity-resolvers.ts) had diverged from the canonical client (src/linear/client.ts) and used the OAuth pattern, breaking acknowledgment-comment posting and silently disabling the Linear bot-identity self-loop check. Also: - Fix the misleading docblock at src/linear/client.ts:8 that documented `Bearer <api_key>` while the code correctly used a bare key — future maintainers would have copied the doc and reintroduced the bug. - Improve linearGraphQL error messages in both the canonical and the router-side helpers to include the response body. Without it the failure surface was just an HTTP status, which made this very bug invisible until source-comparison. Test coverage: - Asserts each of postComment / deleteComment / updateComment sends bare API key, no Bearer prefix. - Asserts the GraphQL mutation body and variables. - Asserts the warning on HTTP failure includes the response body so diagnostics aren't lost again. Out of scope (separate follow-up): linearLabels are stored as free-text names but Linear's issueUpdate.labelIds requires UUIDs, so the `cascade-processing` label isn't being applied. Same shape as the status-mapping bug — needs a wizard UX change to fetch + present a label dropdown. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Required because linearGraphQL now reads response.text() to include the body in HTTP-error messages. The factory was incomplete (only mocked ok/status/json) and would have failed for any real Response.text() call. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rer-prefix fix(linear): send personal API keys without Bearer prefix
The Linear PM wizard stored label mappings as free-text names, but
Linear's issueUpdate.labelIds rejects names and requires UUIDs — so the
cascade-processing / cascade-processed / cascade-error / cascade-ready /
cascade-auto labels were silently never applied to issues.
Brings Linear to the same UX as Trello:
Backend
- linearClient.createLabel(teamId, name, color?) wraps the issueLabelCreate
GraphQL mutation, returning the new label's { id, name, color }.
- integrationsDiscovery.createLinearLabel + createLinearLabels tRPC
mutations mirror the Trello equivalents (single + batch with
per-label error reporting).
Wizard
- LinearFieldMappingStep replaces the free-text label inputs with a
dropdown sourced from linearTeamDetails.labels (already fetched by
discovery). Saves label UUIDs, not names.
- Per-slot "Create" button and a batch "Create All Missing" button use
LINEAR_LABEL_DEFAULTS (name + hex color per slot).
- useLinearLabelCreation hook mirrors useTrelloLabelCreation (single +
batch mutation, ADD_LINEAR_TEAM_LABEL dispatch on success).
Adapter hardening
- LinearPMProvider.resolveLabelId validates the resolved value is UUID-
shaped. If the mapping is missing or holds a name (from a pre-existing
configuration), addLabel/removeLabel short-circuit with a diagnostic
warn instead of passing a bad value to Linear and failing opaquely.
- createWorkItem filters out non-UUID label values (same warn).
Operator note: projects configured before this PR have names stored
(e.g. processing: "cascade-processing"). Re-open the PM wizard → Label
Mappings step; dropdowns will show empty because the saved name no
longer matches a dropdown value. Pick from the dropdown — or click
"Create All Missing" to provision defaults in one click.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…parity feat(linear): full label parity with Trello — dropdown + create-label UX
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
Promotes 57 commits from
devtomain— 570 files, +24k/-6.5k. The bulk is the new Linear PM integration landing end-to-end, plus today's fix chain (wizard state-ID mapping, worker credentials, Linear API auth header, full label parity) and a pile of refactors, dashboard fixes, and dependabot bumps.Headlines
Linear PM integration (end-to-end)
LinearPMProvider, GraphQL client, discovery (teams/states/labels/projects), webhook ingestion, dispatch + trigger handlers (status-changed, label-added, comment-mention), worker wiring.Comment.createwebhook events (Linear-style acknowledgment).Today's fix chain (newly landed; see PRs #1117, #1118, #1119, #1121)
fix(linear): store state IDs, not names, in status mappings— wizard now persists workflow-state UUIDs; webhooks' strict-equality match actually fires.fix(linear): pass projectId to extractProjectIdFromJob for linear jobs— the router now resolves + injects credentials into the worker for Linear jobs.fix(linear): send personal API keys without Bearer prefix— Linear personal API keys must be sent bare; the router's divergent call sites (ack comment + bot-identity resolver) were using OAuth-style headers and failing with HTTP 400.feat(linear): full label parity with Trello— label mappings now use an ID-backed dropdown sourced from the team's labels + per-slot and batch "Create" buttons that provision the CASCADE defaults in one click.JIRA / PM parity
superAdminProceduretoadminProcedure(fix(webhooks): relax webhook CRUD from superAdminProcedure to adminProcedure #1086).Review agent (#1111)
Dashboard
project-agent-configs.tsxandintegration-form.tsxdecomposed (refactor(web): decompose project-agent-configs.tsx and integration-form.tsx #1085).Agent / trigger refactors
LegacyCapabilitiesshim; migrate callers to capability strings (refactor(agents): remove LegacyCapabilities shim and migrate callers to capability strings #1084).agentType === 'review'checks with profile-driven alternatives (refactor: replace hardcoded agentType === 'review' checks with profile-driven alternatives #1082).Dependency bumps
Vite, picomatch, axios, hono, @hono/node-server, follow-redirects (all minor/patch).
Operator action required after deploy
Linear-backed projects on prod need a one-time PM wizard re-run. The wizard's state and label mappings now store UUIDs (not names). Saved configs from before this release have names stored, so the new dropdowns will appear empty. Re-pick each Linear workflow state in Status Mappings, and either re-pick or click Create All Missing in Label Mappings.
Prod Caddy already routes
/linear/*and/sentry/*tocascade-router— committed tomongrel-intelligence/inframain earlier, already deployed to bauer. No action needed.Prod router + worker + dashboard images will be rebuilt and redeployed automatically by
.github/workflows/deploy.ymlon merge tomain.Test plan
curl -i https://api.ca.sca.de.com/linear/webhook→ 200 from router (was 404 before Caddy fix landed).cascade-processinglabel is applied to the issue.🤖 Generated with Claude Code