fix(cli): synthesize Linear PM scope so cascade-tools pm works for Linear projects#1134
Merged
zbigniewsobiecki merged 1 commit intodevfrom Apr 16, 2026
Merged
fix(cli): synthesize Linear PM scope so cascade-tools pm works for Linear projects#1134zbigniewsobiecki merged 1 commit intodevfrom
zbigniewsobiecki merged 1 commit intodevfrom
Conversation
…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>
Codecov Report❌ Patch coverage is
📢 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>
5 tasks
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
Splitting agent for MNG-98 on llmist (Linear-backed) ran today. Every
cascade-tools pm <cmd>from inside the worker container threw: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
src/cli/base.tscastprocess.env.CASCADE_PM_TYPEas'trello' | 'jira' | undefined— butsecretBuilder.ts:68injectsproject.pm.type, so'linear'arrived unboxed at runtime....(pmType === 'jira' && { jira: {...} })had no Linear branch. ForpmType === 'linear'it produced{ pm: { type: 'linear' } }with nolinearfield.LinearIntegration.createProviderthen threwrequires teamId in config.withLinearCredentialswrap: 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.tsinjectedCASCADE_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— whengetLinearConfig(project)is truthy, injectCASCADE_LINEAR_TEAM_ID, optionalCASCADE_LINEAR_PROJECT_ID, andCASCADE_LINEAR_STATUSES. Mirrors the JIRA injection block.src/cli/base.ts:'trello' | 'jira' | undefinedtype lie withPMType | undefined(canonical type atsrc/pm/types.ts:6) so future provider additions can't reintroduce this footgun.withLinearCredentialswrap whenLINEAR_API_KEYis set.LINEAR_API_KEY-based pmType inference as a fallback (mirrors the existing JIRA-baseUrl inference).run()into three small helpers (wrapWithCredentialScopes,resolvePmType,synthesizeProjectFromEnv) to keep cognitive complexity within biome's threshold.Tests
tests/unit/backends/secretBuilder.test.ts(+3): injectsCASCADE_LINEAR_*for Linear projects; omitsPROJECT_IDwhen not set; doesn't inject Linear vars for Trello/JIRA.tests/unit/cli/credential-scoping.test.ts(+3): wraps withwithLinearCredentialswhenLINEAR_API_KEYset; synthesizes populated Linear config; inferspmType=linearwhenLINEAR_API_KEYset withoutCASCADE_PM_TYPE.7851 unit + 524 integration tests pass. lint + typecheck + build clean.
Out of scope
--linear-team-idflag override. Worker-injected env vars suffice for production.Test plan
npm test(7851 pass)npm run test:integration(524 pass)npm run typecheck,npm run lint,npm run buildcleancascade-tools pm read-work-item --workItemId MNG-XXwithout therequires teamIderror.🤖 Generated with Claude Code