Skip to content

fix(cli): synthesize Linear PM scope so cascade-tools pm works for Linear projects#1134

Merged
zbigniewsobiecki merged 1 commit intodevfrom
fix/cli-linear-scope
Apr 16, 2026
Merged

fix(cli): synthesize Linear PM scope so cascade-tools pm works for Linear projects#1134
zbigniewsobiecki merged 1 commit intodevfrom
fix/cli-linear-scope

Conversation

@zbigniewsobiecki
Copy link
Copy Markdown
Member

Summary

Splitting agent for MNG-98 on llmist (Linear-backed) ran today. Every cascade-tools pm <cmd> from inside the worker container threw:

Error: Linear integration requires teamId in config
    at LinearIntegration.createProvider (file:///app/dist/pm/linear/integration.js:65)

The agent fell back to direct Linear API calls and got the work done, but the CLI gadget path was broken for every Linear-backed project.

This is the third generation of the same architectural omission: the CLI was Trello/JIRA-aware long before Linear existed (spec 006 added Linear to the manifest registry but never updated src/cli/base.ts). PR #1131 unblocked the registry (cascade-tools commands actually load), PR #1133 tightened provider validation — both made the latent bug louder.

Three combined causes

  1. Type lie: src/cli/base.ts cast process.env.CASCADE_PM_TYPE as 'trello' | 'jira' | undefined — but secretBuilder.ts:68 injects project.pm.type, so 'linear' arrived unboxed at runtime.
  2. Synthesis only handled JIRA: the conditional spread ...(pmType === 'jira' && { jira: {...} }) had no Linear branch. For pmType === 'linear' it produced { pm: { type: 'linear' } } with no linear field. LinearIntegration.createProvider then threw requires teamId in config.
  3. No withLinearCredentials wrap: GitHub/Trello/JIRA were wrapped, Linear wasn't. Even after the synthesis was fixed, gadget calls would still fail with 'No Linear credentials in scope'.

And on the worker-spawn side, secretBuilder.ts injected CASCADE_JIRA_* env vars but had no Linear equivalent — so even if the CLI tried to read them, they wouldn't be there.

Fix — mirror the JIRA pattern end-to-end

  • src/backends/secretBuilder.ts — when getLinearConfig(project) is truthy, inject CASCADE_LINEAR_TEAM_ID, optional CASCADE_LINEAR_PROJECT_ID, and CASCADE_LINEAR_STATUSES. Mirrors the JIRA injection block.
  • src/cli/base.ts:
    • Replace the 'trello' | 'jira' | undefined type lie with PMType | undefined (canonical type at src/pm/types.ts:6) so future provider additions can't reintroduce this footgun.
    • Add withLinearCredentials wrap when LINEAR_API_KEY is set.
    • Add Linear-config synthesis branch reading the new env vars.
    • Add LINEAR_API_KEY-based pmType inference as a fallback (mirrors the existing JIRA-baseUrl inference).
    • Refactor run() into three small helpers (wrapWithCredentialScopes, resolvePmType, synthesizeProjectFromEnv) to keep cognitive complexity within biome's threshold.

Tests

  • tests/unit/backends/secretBuilder.test.ts (+3): injects CASCADE_LINEAR_* for Linear projects; omits PROJECT_ID when not set; doesn't inject Linear vars for Trello/JIRA.
  • tests/unit/cli/credential-scoping.test.ts (+3): wraps with withLinearCredentials when LINEAR_API_KEY set; synthesizes populated Linear config; infers pmType=linear when LINEAR_API_KEY set without CASCADE_PM_TYPE.

7851 unit + 524 integration tests pass. lint + typecheck + build clean.

Out of scope

  • ❌ Refactoring CLI to load full project config from DB instead of synthesizing from env vars. Bigger change; env-var pattern works for JIRA and is the established convention.
  • --linear-team-id flag override. Worker-injected env vars suffice for production.
  • ❌ Backfilling MNG-98 splitting outputs (the agent created them via direct Linear API and they're correct).

Test plan

  • npm test (7851 pass)
  • npm run test:integration (524 pass)
  • npm run typecheck, npm run lint, npm run build clean
  • After deploy: trigger a Linear-backed agent run on llmist, confirm the agent successfully invokes cascade-tools pm read-work-item --workItemId MNG-XX without the requires teamId error.

🤖 Generated with Claude Code

…near projects

The splitting agent for MNG-98 on llmist (Linear-backed) ran today. Every
`cascade-tools pm <cmd>` call from inside the worker container threw:

```
Error: Linear integration requires teamId in config
    at LinearIntegration.createProvider (file:///app/dist/pm/linear/integration.js:65)
```

The agent fell back to direct Linear API calls and got the work done, but
the CLI gadget path was broken for every Linear-backed project. Three
combined causes:

1. `src/cli/base.ts` cast `process.env.CASCADE_PM_TYPE` as `'trello' |
   'jira' | undefined` — but `secretBuilder.ts` injects the actual
   `project.pm.type`, so `'linear'` was arriving unboxed at runtime.
2. The `pmProject` synthesis only had a JIRA conditional spread; for
   `pmType === 'linear'` it produced `{ pm: { type: 'linear' } }` with no
   `linear` field. `LinearIntegration.createProvider` then read
   `getLinearConfig(project)` → undefined → threw.
3. Even after the synthesis was fixed, no `withLinearCredentials` wrap
   meant gadgets calling Linear API would fail with "No Linear credentials
   in scope". Lines 44-58 wrapped GitHub/Trello/JIRA but not Linear.

And on the worker-spawn side, `secretBuilder.ts:46-53` injected
`CASCADE_JIRA_*` env vars from `getJiraConfig(project)` but had no Linear
equivalent — so even if the CLI tried to read them, they wouldn't be
there.

This is the third generation of the same architectural omission: the CLI
was Trello/JIRA-aware long before Linear existed (spec 006). PR #1131
unblocked the registry (cascade-tools commands actually load), PR #1133
tightened provider validation — both made the latent bug louder. Now
LinearIntegration throws cleanly when it has no teamId, and that throw
surfaces here.

Fix mirrors the JIRA pattern end-to-end:

- `src/backends/secretBuilder.ts` — when `getLinearConfig(project)` is
  truthy, inject `CASCADE_LINEAR_TEAM_ID`, optional
  `CASCADE_LINEAR_PROJECT_ID`, and `CASCADE_LINEAR_STATUSES` into the
  worker's env. Mirrors the JIRA injection block.

- `src/cli/base.ts`:
  - Replace the `'trello' | 'jira' | undefined` type lie with `PMType |
    undefined` (canonical type already exists in `src/pm/types.ts:6`),
    so future provider additions can't reintroduce this footgun.
  - Add a `withLinearCredentials` wrap when `LINEAR_API_KEY` is set,
    mirroring the GitHub/Trello/JIRA wrap pattern.
  - Add a Linear-config synthesis branch, reading
    `CASCADE_LINEAR_TEAM_ID`/`PROJECT_ID`/`STATUSES`.
  - Add `LINEAR_API_KEY`-based pmType inference as a fallback when
    `CASCADE_PM_TYPE` isn't set (mirrors the JIRA-baseUrl inference).
    Worker-spawned use is unaffected because `secretBuilder` always sets
    `CASCADE_PM_TYPE` explicitly; this just helps human-invoked CLI use.
  - Refactor `run()` into three small helpers
    (`wrapWithCredentialScopes`, `resolvePmType`,
    `synthesizeProjectFromEnv`) to keep cognitive complexity inside
    biome's threshold.

Tests: +3 secretBuilder Linear-injection tests, +3 credential-scoping
Linear tests (withLinearCredentials wrap, populated linear synthesis,
LINEAR_API_KEY-based inference). 7851 unit + 524 integration tests pass.
Lint + typecheck + build clean.

Out of scope:
- Refactoring CLI to load full project config from DB instead of
  synthesising from env vars. Bigger change; env-var pattern works for
  JIRA and is the established convention.
- `--linear-team-id` flag override. Worker-injected env vars suffice.
- Backfilling MNG-98 splitting outputs (agent created them via direct
  Linear API; they're correct).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@zbigniewsobiecki zbigniewsobiecki merged commit cfad9b8 into dev Apr 16, 2026
8 checks passed
@zbigniewsobiecki zbigniewsobiecki deleted the fix/cli-linear-scope branch April 16, 2026 20:09
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 16, 2026

Codecov Report

❌ Patch coverage is 73.68421% with 20 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/cli/base.ts 69.23% 20 Missing ⚠️

📢 Thoughts on this report? Let us know!

zbigniewsobiecki added a commit that referenced this pull request Apr 18, 2026
Task 4: src/integrations/entrypoint.ts centralises every PM/SCM/alerting
registration barrel. Router, worker, CLI bootstrap, and dashboard all
side-effect-import this single file — no per-surface list of imports that
can drift when a new provider is added. Previously dashboard.ts didn't
register any PM providers at all, which is also fixed here.

Task 5: tests/unit/integrations/entrypoint-usage.test.ts grep-asserts the
invariant that every process entry file imports src/integrations/entrypoint.js.
Missing this import was the root cause of #1097, #1118, #1131, #1134 —
the test references those bug numbers in its failure message.

Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.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.

1 participant