Scope Linear connections to the active project + fix composer model picker#392
Conversation
…se UI Migrate Linear credentials from the legacy machine-level encrypted store (linear-token.v1.bin) to a project-scoped credential store (.ade/secrets/credentials.json.enc keyed linear.token.v1), clean up legacy files after migration, and surface org/project metadata on connection status. Headless CLI and `ade doctor` readiness now read the project-scoped store with a legacy fallback. Also fix two desktop UI bugs: the composer model picker now keeps a readable floor as the composer narrows (permission collapses to a dot, fast mode to a bolt), and the Work-tab X close button works by suppressing the spurious native drag that was cancelling its click.
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
|
@copilot review but do not make fixes |
📝 WalkthroughWalkthroughThis PR migrates Linear credential storage from machine-level to per-project locations. The CLI now detects credentials from the active project's encrypted store alongside legacy tokens and env vars. Headless services and desktop main are updated to use project-scoped directories. Legacy credential files are cleaned up after migration. Renderer UI and mock Linear API enhancements support the new flow. ChangesLinear credentials: machine-scoped to per-project storage migration
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Capy auto-review is paused for this organization because the monthly auto-review limit has been reached. Increase the limit or turn it off in billing settings to resume automatic reviews. |
There was a problem hiding this comment.
PR Review
Scope: 17 file(s), +262 / −56
Verdict: Needs changes
This PR moves Linear credential storage from the machine-level ~/.ade/secrets store to each project's .ade/secrets, aligns headless/CLI paths with that scope, and tweaks the chat composer model picker (dedicated trigger width + wider container-query breakpoint). The isolation work and tests look sound, but upgrading users who already connected Linear under the old machine store will silently lose that connection with no automatic migration.
🐛 Functionality
[High] Machine-scoped Linear tokens are not migrated to the project store
File: apps/desktop/src/main/main.ts:L2703-L2708
Issue: Desktop now injects EncryptedFileCredentialStore with secretsDir: path.join(adePaths.adeDir, "secrets") instead of machineAdeLayout.secretsDir. linearCredentialService only migrates legacy project files (linear-token.v1.bin, local.secret.yaml) into the injected store; it never reads linear.token.v1 from the old machine credential store. Anyone who connected Linear on current main (machine-scoped) will open a project and see Linear disconnected until they reconnect manually.
Repro: On main, connect Linear in project A (token lands in ~/.ade/secrets/credentials.json.enc). Upgrade to this branch, open project A — linearCredentialService.getToken() returns null; CTO/Linear surfaces show disconnected.
Fix: On project load, if the project store has no linear.token.v1, read it once from the machine EncryptedFileCredentialStore (machineAdeLayout.secretsDir), copy auth mode / refresh / OAuth client keys into the project store, then delete the machine Linear keys (or mark migrated). Mirror the same fallback in createHeadlessLinearCredentialService for headless runtimes that previously shared one machine token.
[High] ade doctor / readiness ignores machine-store Linear tokens
File: apps/ade-cli/src/cli.ts:L9058-L9094
Issue: checkLinearReadiness only checks project .ade/secrets (linear-token.v1.bin, credentials.json.enc via hasProjectCredentialStoreValue) and env vars. It does not look at ~/.ade/secrets, so doctor reports Linear as not ready for the same upgraded users above even though a usable token still exists on disk.
Repro: Same setup as above; run ade doctor (or any path using checkLinearReadiness) — ready: false despite ~/.ade/secrets still holding linear.token.v1.
Fix: Either include a one-time machine-store probe (matching the desktop migration) or document that post-upgrade reconnect is required and remove the README claim about "legacy machine-level encrypted token" readiness (apps/ade-cli/README.md:L323) if machine scope is no longer supported.
🎨 UI/UX
[Low] Mid-width composer toolbar may scroll more before controls collapse
File: apps/desktop/src/renderer/index.css:L3424-L3446
Issue: The chat-composer container breakpoint for icon-only permission/fast toggles moved from 400px to 560px. Between ~401–560px wide footers, text labels stay visible while the model picker now enforces min-w-[4.5rem] (AgentChatComposer.tsx:L460), so users are more likely to rely on horizontal scroll in the overflow-x toolbar row.
Fix: If scroll feels worse in practice, consider collapsing permission/fast labels at an intermediate breakpoint (e.g. 480px) while keeping the model picker's minimum width, or hide reasoning picker text first.
Notes
- Good coverage: headless isolation test ensures
~/.ade/secretslinear.token.v1does not bleed into a second project (headlessLinearServices.test.ts). - Legacy project
linear-token.v1.binmigration plus cleanup inlinearCredentialService.tsis a nice touch; the gap is specifically the prior machine store used onmain. - Browser mock additions for Linear connect/disconnect are helpful for Vite preview only; no production impact.
- Commit message mentions tab-close UI, but this diff does not include tab-close changes — likely from an earlier squash.
Sent by Cursor Automation: BUGBOT in Versic
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
apps/desktop/src/main/services/cto/linearCredentialService.ts (1)
282-323:⚠️ Potential issue | 🟠 Major | 🏗️ Heavy liftThis migration no longer points at the actual legacy desktop files.
tokenPath/oauthClientPathare resolved fromargs.adeDir, and the stack context saysmain.tsnow passes the active project's.ade. That means this block only checks<project>/.ade/secrets/..., but the legacy files being migrated were machine-scoped. Existing desktop tokens/oauth client config will be missed and never cleaned up.Please plumb the old machine-level secrets dir into this migration path, or add an explicit fallback lookup for the pre-migration location before marking the project migrated.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/desktop/src/main/services/cto/linearCredentialService.ts` around lines 282 - 323, migrateLegacyProjectCredentialsIfNeeded currently resolves tokenPath/oauthClientPath from args.adeDir (project .ade) so it misses machine-scoped legacy files; update the function to also check the old machine-level secrets location before deciding migration is complete: compute the legacy machineSecretsDir (the pre-migration location used by readEncryptedToken/readStoredOAuthClientCredentials), and if tokenPath/oauthClientPath inside args.adeDir do not exist, attempt the same checks (readMachineToken, readEncryptedToken, readStoredOAuthClientCredentials, readOAuthConfigFileCredentials) against the machineSecretsDir; only call markProjectMigrated() (and unlinkIfExists) after both project and machine-level lookup/fallbacks have been attempted, preserving the existing safeStorage.isEncryptionAvailable() logic and using the same helpers (persistMachineToken, persistMachineOAuthClientCredentials, unlinkIfExists) so behavior is consistent.apps/ade-cli/src/headlessLinearServices.ts (1)
1142-1198:⚠️ Potential issue | 🟠 Major | ⚡ Quick winRestore the legacy machine-store fallback here.
This change makes headless Linear auth read only
<project>/.ade/secretsplus env vars. Existing users with a legacy machine-scopedcredentials.json.encwill now failgetTokenOrThrow()until they re-authenticate, even though the PR objective calls for a legacy fallback during the transition.Possible fix
function createHeadlessLinearCredentialService(args: { adeDir: string; }): HeadlessLinearCredentialService { - const credentialStore = new EncryptedFileCredentialStore({ + const projectCredentialStore = new EncryptedFileCredentialStore({ secretsDir: path.join(args.adeDir, "secrets"), }); + const legacyMachineCredentialStore = new EncryptedFileCredentialStore(); const tokenKey = "linear.token.v1"; @@ const readCredential = (key: string): string | null => { try { - const stored = credentialStore.getSync(key); + const stored = + projectCredentialStore.getSync(key) ?? + legacyMachineCredentialStore.getSync(key); tokenDecryptionFailed = false; return stored?.trim() || null; } catch { @@ if (value?.trim()) { - credentialStore.setSync(key, value.trim()); + projectCredentialStore.setSync(key, value.trim()); } else { - credentialStore.deleteSync(key); + projectCredentialStore.deleteSync(key); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/ade-cli/src/headlessLinearServices.ts` around lines 1142 - 1198, readToken currently only checks the new EncryptedFileCredentialStore and env/override, so restore the legacy machine-store fallback by attempting to read from the legacy store when readCredential(tokenKey) returns null; specifically, in readToken (and similarly for refreshToken/oauthClient/authMode/tokenExpiresAt usage), if stored is falsy then instantiate or call the legacy MachineCredentialStore equivalent (e.g., MachineCredentialStore.getSync or EncryptedMachineCredentialStore.getSync) to read tokenKey, and if found migrate it by writing it into credentialStore via writeCredential(tokenKey, legacyValue) before returning { token: legacyValue, source: "stored" } (or "legacy" if you prefer); ensure you handle decryption failures the same way (preserve tokenDecryptionFailed) and do not break the existing env/override logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/ade-cli/src/cli.ts`:
- Around line 9059-9067: The legacy Linear token check is using the
project-scoped layout.secretsDir but the legacy binary lived in the
machine-scoped secrets location; update the fallback check to also look in the
machine-level secrets path returned by resolveAdeLayout (or the layout property
that represents the machine secrets directory) for "linear-token.v1.bin" and/or
combine both locations so legacyEncryptedTokenPresent correctly detects
unmigrated machine-scoped credentials; adjust the existence check around
resolveAdeLayout/layout.secretsDir and ensure hasProjectCredentialStoreValue
remains used for the project-scoped key "linear.token.v1".
In `@apps/desktop/src/renderer/browserMock.ts`:
- Around line 4880-4901: The mock currently returns one-off snapshots in
setLinearToken and clearLinearToken while getLinearConnectionStatus continues to
read the unchanged MOCK_LINEAR_CONNECTION, causing stale connected state; change
the mock to persist state by replacing or mutating the shared mock state object
(e.g., update MOCK_LINEAR_CONNECTION or introduce a mutable mockLinearState)
inside setLinearToken and clearLinearToken so that getLinearConnectionStatus
reads the updated state thereafter (refer to setLinearToken, clearLinearToken,
getLinearConnectionStatus, and MOCK_LINEAR_CONNECTION to locate the code).
---
Outside diff comments:
In `@apps/ade-cli/src/headlessLinearServices.ts`:
- Around line 1142-1198: readToken currently only checks the new
EncryptedFileCredentialStore and env/override, so restore the legacy
machine-store fallback by attempting to read from the legacy store when
readCredential(tokenKey) returns null; specifically, in readToken (and similarly
for refreshToken/oauthClient/authMode/tokenExpiresAt usage), if stored is falsy
then instantiate or call the legacy MachineCredentialStore equivalent (e.g.,
MachineCredentialStore.getSync or EncryptedMachineCredentialStore.getSync) to
read tokenKey, and if found migrate it by writing it into credentialStore via
writeCredential(tokenKey, legacyValue) before returning { token: legacyValue,
source: "stored" } (or "legacy" if you prefer); ensure you handle decryption
failures the same way (preserve tokenDecryptionFailed) and do not break the
existing env/override logic.
In `@apps/desktop/src/main/services/cto/linearCredentialService.ts`:
- Around line 282-323: migrateLegacyProjectCredentialsIfNeeded currently
resolves tokenPath/oauthClientPath from args.adeDir (project .ade) so it misses
machine-scoped legacy files; update the function to also check the old
machine-level secrets location before deciding migration is complete: compute
the legacy machineSecretsDir (the pre-migration location used by
readEncryptedToken/readStoredOAuthClientCredentials), and if
tokenPath/oauthClientPath inside args.adeDir do not exist, attempt the same
checks (readMachineToken, readEncryptedToken, readStoredOAuthClientCredentials,
readOAuthConfigFileCredentials) against the machineSecretsDir; only call
markProjectMigrated() (and unlinkIfExists) after both project and machine-level
lookup/fallbacks have been attempted, preserving the existing
safeStorage.isEncryptionAvailable() logic and using the same helpers
(persistMachineToken, persistMachineOAuthClientCredentials, unlinkIfExists) so
behavior is consistent.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: dfaff4b8-51d5-4015-9281-bda75465a965
⛔ Files ignored due to path filters (5)
docs/features/cto/README.mdis excluded by!docs/**docs/features/cto/linear-integration.mdis excluded by!docs/**docs/features/linear-integration/README.mdis excluded by!docs/**docs/features/onboarding-and-settings/README.mdis excluded by!docs/**docs/features/sync-and-multi-device/README.mdis excluded by!docs/**
📒 Files selected for processing (12)
apps/ade-cli/README.mdapps/ade-cli/src/cli.test.tsapps/ade-cli/src/cli.tsapps/ade-cli/src/headlessLinearServices.test.tsapps/ade-cli/src/headlessLinearServices.tsapps/desktop/src/main/main.tsapps/desktop/src/main/services/cto/linearAuth.test.tsapps/desktop/src/main/services/cto/linearCredentialService.tsapps/desktop/src/renderer/browserMock.tsapps/desktop/src/renderer/components/chat/AgentChatComposer.tsxapps/desktop/src/renderer/index.csscto/linear.mdx
| const { resolveAdeLayout } = requireAdeLayout(); | ||
| const layout = resolveAdeLayout(projectRoot); | ||
| const encryptedTokenPresent = fs.existsSync( | ||
| const legacyEncryptedTokenPresent = fs.existsSync( | ||
| path.join(layout.secretsDir, "linear-token.v1.bin"), | ||
| ); | ||
| const projectCredentialStoreTokenPresent = hasProjectCredentialStoreValue( | ||
| layout.secretsDir, | ||
| "linear.token.v1", | ||
| ); |
There was a problem hiding this comment.
Use the machine-scoped path for the legacy Linear token fallback.
layout.secretsDir here is the active project's .ade/secrets, but linear-token.v1.bin is the legacy machine-level store per this migration. In this form, ade doctor / ade auth status will miss still-unmigrated legacy credentials and incorrectly report Linear as unavailable.
Suggested fix
function checkLinearReadiness(projectRoot: string): ReadinessCheck {
const { resolveAdeLayout } = requireAdeLayout();
const layout = resolveAdeLayout(projectRoot);
+ const machineLayout = resolveMachineAdeLayout();
const legacyEncryptedTokenPresent = fs.existsSync(
- path.join(layout.secretsDir, "linear-token.v1.bin"),
+ path.join(machineLayout.secretsDir, "linear-token.v1.bin"),
);
const projectCredentialStoreTokenPresent = hasProjectCredentialStoreValue(
layout.secretsDir,
"linear.token.v1",
);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const { resolveAdeLayout } = requireAdeLayout(); | |
| const layout = resolveAdeLayout(projectRoot); | |
| const encryptedTokenPresent = fs.existsSync( | |
| const legacyEncryptedTokenPresent = fs.existsSync( | |
| path.join(layout.secretsDir, "linear-token.v1.bin"), | |
| ); | |
| const projectCredentialStoreTokenPresent = hasProjectCredentialStoreValue( | |
| layout.secretsDir, | |
| "linear.token.v1", | |
| ); | |
| const { resolveAdeLayout } = requireAdeLayout(); | |
| const layout = resolveAdeLayout(projectRoot); | |
| const machineLayout = resolveMachineAdeLayout(); | |
| const legacyEncryptedTokenPresent = fs.existsSync( | |
| path.join(machineLayout.secretsDir, "linear-token.v1.bin"), | |
| ); | |
| const projectCredentialStoreTokenPresent = hasProjectCredentialStoreValue( | |
| layout.secretsDir, | |
| "linear.token.v1", | |
| ); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/ade-cli/src/cli.ts` around lines 9059 - 9067, The legacy Linear token
check is using the project-scoped layout.secretsDir but the legacy binary lived
in the machine-scoped secrets location; update the fallback check to also look
in the machine-level secrets path returned by resolveAdeLayout (or the layout
property that represents the machine secrets directory) for
"linear-token.v1.bin" and/or combine both locations so
legacyEncryptedTokenPresent correctly detects unmigrated machine-scoped
credentials; adjust the existence check around
resolveAdeLayout/layout.secretsDir and ensure hasProjectCredentialStoreValue
remains used for the project-scoped key "linear.token.v1".
| setLinearToken: resolvedArg({ | ||
| ...MOCK_LINEAR_CONNECTION, | ||
| authMode: "manual" as const, | ||
| message: "Linear token accepted in browser preview.", | ||
| }), | ||
| clearLinearToken: resolvedArg({ | ||
| tokenStored: false, | ||
| connected: false, | ||
| viewerId: null, | ||
| viewerName: null, | ||
| organizationId: null, | ||
| organizationName: null, | ||
| organizationUrlKey: null, | ||
| organizationLogoUrl: null, | ||
| projectCount: 0, | ||
| projectPreview: [], | ||
| checkedAt: now, | ||
| authMode: null, | ||
| oauthAvailable: true, | ||
| tokenExpiresAt: null, | ||
| message: "Linear disconnected in browser preview.", | ||
| }), |
There was a problem hiding this comment.
Persist mock Linear auth state across calls.
Line 4880 and Line 4885 return one-off snapshots, but getLinearConnectionStatus (Line 4879) still reads the unchanged MOCK_LINEAR_CONNECTION. After clearLinearToken, a subsequent status read will incorrectly show connected=true.
Suggested fix
const MOCK_LINEAR_CONNECTION = {
tokenStored: true,
connected: true,
@@
message: null,
};
+let mockLinearConnectionState: any = { ...MOCK_LINEAR_CONNECTION };
@@
- getLinearConnectionStatus: resolvedArg(MOCK_LINEAR_CONNECTION),
- setLinearToken: resolvedArg({
- ...MOCK_LINEAR_CONNECTION,
- authMode: "manual" as const,
- message: "Linear token accepted in browser preview.",
- }),
- clearLinearToken: resolvedArg({
- tokenStored: false,
- connected: false,
- viewerId: null,
- viewerName: null,
- organizationId: null,
- organizationName: null,
- organizationUrlKey: null,
- organizationLogoUrl: null,
- projectCount: 0,
- projectPreview: [],
- checkedAt: now,
- authMode: null,
- oauthAvailable: true,
- tokenExpiresAt: null,
- message: "Linear disconnected in browser preview.",
- }),
+ getLinearConnectionStatus: async () => ({ ...mockLinearConnectionState }),
+ setLinearToken: async () => {
+ mockLinearConnectionState = {
+ ...MOCK_LINEAR_CONNECTION,
+ checkedAt: new Date().toISOString(),
+ authMode: "manual" as const,
+ message: "Linear token accepted in browser preview.",
+ };
+ return { ...mockLinearConnectionState };
+ },
+ clearLinearToken: async () => {
+ mockLinearConnectionState = {
+ tokenStored: false,
+ connected: false,
+ viewerId: null,
+ viewerName: null,
+ organizationId: null,
+ organizationName: null,
+ organizationUrlKey: null,
+ organizationLogoUrl: null,
+ projectCount: 0,
+ projectPreview: [],
+ checkedAt: new Date().toISOString(),
+ authMode: null,
+ oauthAvailable: true,
+ tokenExpiresAt: null,
+ message: "Linear disconnected in browser preview.",
+ };
+ return { ...mockLinearConnectionState };
+ },🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/desktop/src/renderer/browserMock.ts` around lines 4880 - 4901, The mock
currently returns one-off snapshots in setLinearToken and clearLinearToken while
getLinearConnectionStatus continues to read the unchanged
MOCK_LINEAR_CONNECTION, causing stale connected state; change the mock to
persist state by replacing or mutating the shared mock state object (e.g.,
update MOCK_LINEAR_CONNECTION or introduce a mutable mockLinearState) inside
setLinearToken and clearLinearToken so that getLinearConnectionStatus reads the
updated state thereafter (refer to setLinearToken, clearLinearToken,
getLinearConnectionStatus, and MOCK_LINEAR_CONNECTION to locate the code).
| function hasProjectCredentialStoreValue( | ||
| secretsDir: string, | ||
| key: string, | ||
| ): boolean { | ||
| const credentialsPath = path.join(secretsDir, "credentials.json.enc"); | ||
| const machineKeyPath = path.join(secretsDir, ".machine-key"); | ||
| if (!fs.existsSync(credentialsPath) || !fs.existsSync(machineKeyPath)) { | ||
| return false; | ||
| } | ||
| try { | ||
| const credentialStore = new EncryptedFileCredentialStore({ | ||
| credentialsPath, | ||
| machineKeyPath, | ||
| }); | ||
| return Boolean(credentialStore.getSync(key)?.trim()); | ||
| } catch { | ||
| return false; | ||
| } | ||
| } |
There was a problem hiding this comment.
hasProjectCredentialStoreValue hardcodes "credentials.json.enc" and ".machine-key" — the same values as DEFAULT_CREDENTIALS_FILE / DEFAULT_MACHINE_KEY_FILE in credentialStore.ts. If those constants are ever renamed, this function will silently return false for every project (making ade doctor always report no credentials). The existsSync pre-check is still needed to avoid triggering readOrCreateMachineKey on a fresh project dir, but the store itself could be constructed with just { secretsDir } to stay DRY.
| function hasProjectCredentialStoreValue( | |
| secretsDir: string, | |
| key: string, | |
| ): boolean { | |
| const credentialsPath = path.join(secretsDir, "credentials.json.enc"); | |
| const machineKeyPath = path.join(secretsDir, ".machine-key"); | |
| if (!fs.existsSync(credentialsPath) || !fs.existsSync(machineKeyPath)) { | |
| return false; | |
| } | |
| try { | |
| const credentialStore = new EncryptedFileCredentialStore({ | |
| credentialsPath, | |
| machineKeyPath, | |
| }); | |
| return Boolean(credentialStore.getSync(key)?.trim()); | |
| } catch { | |
| return false; | |
| } | |
| } | |
| function hasProjectCredentialStoreValue( | |
| secretsDir: string, | |
| key: string, | |
| ): boolean { | |
| // Pre-check both files so we don't trigger readOrCreateMachineKey on a | |
| // fresh project directory that has never stored credentials. | |
| const credentialsPath = path.join(secretsDir, "credentials.json.enc"); | |
| const machineKeyPath = path.join(secretsDir, ".machine-key"); | |
| if (!fs.existsSync(credentialsPath) || !fs.existsSync(machineKeyPath)) { | |
| return false; | |
| } | |
| try { | |
| const credentialStore = new EncryptedFileCredentialStore({ secretsDir }); | |
| return Boolean(credentialStore.getSync(key)?.trim()); | |
| } catch { | |
| return false; | |
| } | |
| } |
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/ade-cli/src/cli.ts
Line: 9097-9115
Comment:
`hasProjectCredentialStoreValue` hardcodes `"credentials.json.enc"` and `".machine-key"` — the same values as `DEFAULT_CREDENTIALS_FILE` / `DEFAULT_MACHINE_KEY_FILE` in `credentialStore.ts`. If those constants are ever renamed, this function will silently return `false` for every project (making `ade doctor` always report no credentials). The `existsSync` pre-check is still needed to avoid triggering `readOrCreateMachineKey` on a fresh project dir, but the store itself could be constructed with just `{ secretsDir }` to stay DRY.
```suggestion
function hasProjectCredentialStoreValue(
secretsDir: string,
key: string,
): boolean {
// Pre-check both files so we don't trigger readOrCreateMachineKey on a
// fresh project directory that has never stored credentials.
const credentialsPath = path.join(secretsDir, "credentials.json.enc");
const machineKeyPath = path.join(secretsDir, ".machine-key");
if (!fs.existsSync(credentialsPath) || !fs.existsSync(machineKeyPath)) {
return false;
}
try {
const credentialStore = new EncryptedFileCredentialStore({ secretsDir });
return Boolean(credentialStore.getSync(key)?.trim());
} catch {
return false;
}
}
```
How can I resolve this? If you propose a fix, please make it concise.The doctor readiness summary described a "legacy machine-level encrypted token" fallback, but checkLinearReadiness only probes the active project's .ade/secrets (linear.token.v1 plus the legacy project-scoped linear-token.v1.bin file) and headless env vars. Describe the legacy file as project-scoped to match the project-isolation design. Also remove the mkdtemp project root the readiness test leaves behind. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
Thanks for the reviews. I verified each item against the code rather than taking severity at face value — summary of what changed and what I'm intentionally leaving as-is: Addressed (pushed in a2429e1):
Intentionally not changed (with rationale):
|
|
@codex review |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |


Summary
linear-token.v1.bin) to a project-scoped credential store (.ade/secrets/credentials.json.enc, keyedlinear.token.v1), clean up legacy files after migration, and surface org/project metadata on connection status.ade doctorLinear readiness now read the project-scoped store with a legacy fallback (env token still honored).Test plan
cli+headlessLinearServices(188), desktoplinearAuth+ chat composer (476)🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
.ade/secretsfor improved isolationBug Fixes
Documentation
Style
Greptile Summary
This PR scopes Linear credentials from a shared machine-level store to the active project's
.ade/secretsdirectory, allowing separate ADE projects to connect to separate Linear workspaces. It also fixes the composer model picker to maintain a readable minimum width as the container narrows.linearCredentialServiceand the headless CLI both now point theirEncryptedFileCredentialStoreat<project>/.ade/secretsinstead of~/.ade/secrets.unlinkIfExistscalls are added topersistToken,persistOAuthClientCredentials, and the migration routine to clean up legacy.binfiles on write.checkLinearReadinessin the CLI now checks the project-scoped JSON store in addition to legacy binary files and env vars, withhasProjectCredentialStoreValueguarded by existence checks to avoid decryption on fresh directories.COMPOSER_MODEL_TRIGGERconstant addsmin-w-[4.5rem]so the model name keeps a readable floor, and the container-query breakpoint for icon-only permission/fast labels is raised from 400 px to 560 px.Confidence Score: 3/5
Existing users whose Linear credentials were already in the machine-level store will silently lose their connection with no migration path in the new code.
The credential store destination changes from machine-level (~/.ade/secrets) to project-level (.ade/secrets) but the migration function only reads from project-scoped .bin files. Any user who connected Linear while on the previous build will find both the .bin files gone and the new project store empty, appearing disconnected with no warning or recovery path.
apps/desktop/src/main/services/cto/linearCredentialService.ts — the migration function needs a read-from-old-machine-store step; apps/desktop/src/main/main.ts — the credentialStore path change is what triggers the gap.
Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A[App startup / linearCredentialService init] --> B{credentialStore injected?} B -- No --> C[Legacy path: readEncryptedToken from .bin] B -- Yes --> D{Project already migrated?} D -- Yes --> E[readMachineToken from project store] D -- No --> F{readMachineToken from project store} F -- Found --> E F -- Not found --> G{linear-token.v1.bin exists?} G -- Yes --> H[Migrate .bin → project store\nunlinkIfExists .bin] G -- No --> I{local.secret.yaml?} I -- Yes --> J[Migrate yaml → project store] I -- No --> K[No token found ⚠️] H --> L[markProjectMigrated] J --> L K --> L M[OLD machine-level credentials.json.enc\n~/.ade/secrets/] -. never read .-> K style M fill:#f99,stroke:#c00 style K fill:#f99,stroke:#c00Prompt To Fix All With AI
Reviews (2): Last reviewed commit: "Correct Linear readiness doc and clean u..." | Re-trigger Greptile