Skip to content

Scope Linear connections to the active project + fix composer model picker#392

Merged
arul28 merged 2 commits into
mainfrom
ade/project-scoped-linear-connections-a45c3653
May 29, 2026
Merged

Scope Linear connections to the active project + fix composer model picker#392
arul28 merged 2 commits into
mainfrom
ade/project-scoped-linear-connections-a45c3653

Conversation

@arul28
Copy link
Copy Markdown
Owner

@arul28 arul28 commented May 29, 2026

Summary

  • 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 Linear readiness now read the project-scoped store with a legacy fallback (env token still honored).
  • Fix the composer model picker so it keeps a readable floor as the composer narrows: permission collapses to a dot, fast mode to a bolt, instead of clipping the model name.

Test plan

  • typecheck (desktop, ade-cli, web) — clean
  • desktop lint — 0 errors
  • builds (desktop, ade-cli, web) — succeed
  • scoped tests: ade-cli cli + headlessLinearServices (188), desktop linearAuth + chat composer (476)
  • CI full sharded suite

🤖 Generated with Claude Code

ADE   Open in ADE  ·  ade/project-scoped-linear-connections-a45c3653 branch  ·  PR #392

Summary by CodeRabbit

  • New Features

    • Linear credentials now stored per-project in .ade/secrets for improved isolation
    • Enhanced credential detection for "doctor readiness" checks
  • Bug Fixes

    • Legacy Linear credential files automatically cleaned up during migration
  • Documentation

    • Updated Linear authentication documentation to reflect project-local credential storage
  • Style

    • Improved chat composer responsive layout for various container sizes

Review Change Stack

Greptile Summary

This PR scopes Linear credentials from a shared machine-level store to the active project's .ade/secrets directory, 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.

  • Credential migration: linearCredentialService and the headless CLI both now point their EncryptedFileCredentialStore at <project>/.ade/secrets instead of ~/.ade/secrets. unlinkIfExists calls are added to persistToken, persistOAuthClientCredentials, and the migration routine to clean up legacy .bin files on write.
  • Doctor readiness: checkLinearReadiness in the CLI now checks the project-scoped JSON store in addition to legacy binary files and env vars, with hasProjectCredentialStoreValue guarded by existence checks to avoid decryption on fresh directories.
  • Composer UI: A new COMPOSER_MODEL_TRIGGER constant adds min-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

Filename Overview
apps/desktop/src/main/services/cto/linearCredentialService.ts Adds unlinkIfExists helper and calls it on every persistToken/persistOAuthClientCredentials write and during migration to clean up legacy .bin files; migration logic is otherwise unchanged but the credentialStore destination is now project-scoped — missing a read-from-old-machine-store step for users who were already on the intermediate machine-level store.
apps/desktop/src/main/main.ts Changes linearCredentialService credential store from machineAdeLayout.secretsDir to path.join(adePaths.adeDir, 'secrets'), scoping Linear credentials to the active project; other services (GitHub, API keys) still correctly use the machine-level store.
apps/ade-cli/src/cli.ts Adds hasProjectCredentialStoreValue helper and exports checkLinearReadiness; checkLinearReadiness now checks both legacy binary and new project-scoped JSON store, returning enriched details. String paths inside hasProjectCredentialStoreValue are hardcoded (noted in a previous thread).
apps/ade-cli/src/headlessLinearServices.test.ts Updates the existing credentials test and adds two new isolation tests; temp directories created by mkdtempSync in the new tests are not cleaned up in their finally blocks.
apps/desktop/src/renderer/components/chat/AgentChatComposer.tsx Introduces COMPOSER_MODEL_TRIGGER with a min-w-[4.5rem] floor so the model name stays readable as the composer narrows, replacing the previous min-w-0 on the shared COMPOSER_TOOLBAR_PICKER_TRIGGER class.
apps/desktop/src/renderer/index.css Widens the container-query breakpoint from 400/401 px to 560/561 px so permission and fast-mode labels collapse to icon-only at a larger composer width.
apps/desktop/src/renderer/browserMock.ts Adds org metadata fields to MOCK_LINEAR_CONNECTION, extracts MOCK_LINEAR_PROJECTS as a named constant, and adds setLinearToken/clearLinearToken mock handlers with the new shape.
apps/desktop/src/main/services/cto/linearAuth.test.ts Adds assertions that legacy .bin files are deleted after migration; clean and correct.
apps/ade-cli/src/cli.test.ts Adds a test verifying checkLinearReadiness detects project-local credentials; correctly creates and removes temp directory in the finally block.

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:#c00
Loading

Fix All in Claude Code

Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
apps/desktop/src/main/services/cto/linearCredentialService.ts:282-323
**No migration path from the old machine-level credential store**

`migrateLegacyProjectCredentialsIfNeeded` only looks for `linear-token.v1.bin` (project-level binary) and then falls through to `local.secret.yaml`. It has no path that reads from `~/.ade/secrets/credentials.json.enc` — the machine-level `EncryptedFileCredentialStore` that the previous version of `main.ts` was writing to (`secretsDir: machineAdeLayout.secretsDir`).

Any user whose credentials were already migrated by the old code (project `.bin` → machine-level JSON store) will find the machine-level `credentials.json.enc` unread, the `.bin` files already deleted, and the new project-level store empty — silently appearing disconnected after upgrading to this build. A read-and-copy step from the old machine-level store (keyed by the project root to avoid cross-project bleed) would close this gap.

### Issue 2 of 2
apps/ade-cli/src/headlessLinearServices.test.ts:235-267
**Temp directories not removed in new tests**

Both new tests — "reads Linear credentials from the project store…" and "does not share Linear credentials between headless projects" — create `mkdtemp` directories (`projectRoot`, `projectOneRoot`, `projectTwoRoot`, and the `ADE_HOME` temp dir) but never call `fs.rmSync` on them in the `finally` block. Since credential files are written into these dirs, they accumulate encrypted data in `/tmp`. The analogous `cli.test.ts` test added in this same PR does clean up correctly; the same pattern should apply here.

Reviews (2): Last reviewed commit: "Correct Linear readiness doc and clean u..." | Re-trigger Greptile

…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.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 29, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
ade Ignored Ignored Preview May 29, 2026 12:45am

@arul28
Copy link
Copy Markdown
Owner Author

arul28 commented May 29, 2026

@copilot review but do not make fixes

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 29, 2026

📝 Walkthrough

Walkthrough

This 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.

Changes

Linear credentials: machine-scoped to per-project storage migration

Layer / File(s) Summary
CLI credentials detection from project store
apps/ade-cli/src/cli.ts, apps/ade-cli/src/cli.test.ts, apps/ade-cli/README.md
checkLinearReadiness now reads from the project's encrypted credential store (credentials.json.enc + .machine-key) and reports granular details distinguishing legacy encrypted tokens, credential-store values, and environment variables. The function is exported for reuse in tests and external modules. A new test verifies detection of project-local credentials.
Headless services project-scoped credentials
apps/ade-cli/src/headlessLinearServices.ts, apps/ade-cli/src/headlessLinearServices.test.ts
createHeadlessLinearCredentialService accepts an adeDir parameter and configures the credential store to use ${adeDir}/secrets instead of the default machine location. Test factory createDeps is refactored to support project-specific overrides. Tests verify project isolation and that Linear credentials do not leak between projects.
Desktop credential service legacy file cleanup
apps/desktop/src/main/services/cto/linearCredentialService.ts, apps/desktop/src/main/services/cto/linearAuth.test.ts
Adds unlinkIfExists helper to delete legacy credential files (linear-token.v1.bin, linear-oauth-client.v1.bin) after migration and on each persist operation. Tests verify that legacy files are removed after credential migration.
Desktop main process project-scoped wiring
apps/desktop/src/main/main.ts
linearCredentialService is configured with an EncryptedFileCredentialStore rooted under adePaths.adeDir/secrets instead of machine-level machineAdeLayout.secretsDir.
Mock Linear API and renderer enhancements
apps/desktop/src/renderer/browserMock.ts, apps/desktop/src/renderer/components/chat/AgentChatComposer.tsx, apps/desktop/src/renderer/index.css
Mock Linear connection extended with organization metadata and MOCK_LINEAR_PROJECTS constant. Mock API methods setLinearToken and clearLinearToken return connection snapshots. Model picker trigger CSS class updated to COMPOSER_MODEL_TRIGGER for correct sizing. Container query breakpoints adjusted from 400px/401px to 560px/561px for responsive layout.
User documentation for per-project storage
cto/linear.mdx
Linear OAuth documentation updated to describe per-project token storage under .ade/secrets.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes


Possibly related PRs

  • arul28/ADE#133: Both PRs enhance Linear credential detection to include environment variable fallbacks; this PR adds project-scoped credential store detection in checkLinearReadiness, while PR #133 adds env fallback in linearCredentialService.getStoredToken().

Suggested labels

desktop, docs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately captures the two main changes: scoping Linear connections to the active project and fixing the composer model picker.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ade/project-scoped-linear-connections-a45c3653

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@capy-ai
Copy link
Copy Markdown

capy-ai Bot commented May 29, 2026

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.

Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/secrets linear.token.v1 does not bleed into a second project (headlessLinearServices.test.ts).
  • Legacy project linear-token.v1.bin migration plus cleanup in linearCredentialService.ts is a nice touch; the gap is specifically the prior machine store used on main.
  • 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.
Open in Web View Automation 

Sent by Cursor Automation: BUGBOT in Versic

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 lift

This migration no longer points at the actual legacy desktop files.

tokenPath/oauthClientPath are resolved from args.adeDir, and the stack context says main.ts now 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 win

Restore the legacy machine-store fallback here.

This change makes headless Linear auth read only <project>/.ade/secrets plus env vars. Existing users with a legacy machine-scoped credentials.json.enc will now fail getTokenOrThrow() 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

📥 Commits

Reviewing files that changed from the base of the PR and between 041d0ba and 43c44c6.

⛔ Files ignored due to path filters (5)
  • docs/features/cto/README.md is excluded by !docs/**
  • docs/features/cto/linear-integration.md is excluded by !docs/**
  • docs/features/linear-integration/README.md is excluded by !docs/**
  • docs/features/onboarding-and-settings/README.md is excluded by !docs/**
  • docs/features/sync-and-multi-device/README.md is excluded by !docs/**
📒 Files selected for processing (12)
  • apps/ade-cli/README.md
  • apps/ade-cli/src/cli.test.ts
  • apps/ade-cli/src/cli.ts
  • apps/ade-cli/src/headlessLinearServices.test.ts
  • apps/ade-cli/src/headlessLinearServices.ts
  • apps/desktop/src/main/main.ts
  • apps/desktop/src/main/services/cto/linearAuth.test.ts
  • apps/desktop/src/main/services/cto/linearCredentialService.ts
  • apps/desktop/src/renderer/browserMock.ts
  • apps/desktop/src/renderer/components/chat/AgentChatComposer.tsx
  • apps/desktop/src/renderer/index.css
  • cto/linear.mdx

Comment thread apps/ade-cli/src/cli.ts
Comment on lines 9059 to +9067
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",
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

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.

Suggested change
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".

Comment on lines +4880 to +4901
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.",
}),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

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).

Comment thread apps/ade-cli/src/cli.ts
Comment on lines +9097 to +9115
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;
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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.

Suggested change
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.

Fix in Claude Code

Comment thread apps/ade-cli/src/cli.test.ts
Comment thread apps/desktop/src/main/services/cto/linearCredentialService.ts
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>
@arul28
Copy link
Copy Markdown
Owner Author

arul28 commented May 29, 2026

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):

  • Greptile — cli.test.ts temp dir leak: fixed; the readiness test now rmSyncs its mkdtemp project root in finally.
  • Doc accuracy (apps/ade-cli/README.md): the doctor summary said readiness used a "legacy machine-level encrypted token." That's inaccurate — checkLinearReadiness only probes the active project's .ade/secrets (linear.token.v1 and the legacy project-scoped linear-token.v1.bin) plus headless env vars. Reworded to "a legacy project-scoped encrypted token file."

Intentionally not changed (with rationale):

  • Cursor [High] "machine tokens not migrated to the project store" / CodeRabbit "use the machine-scoped path for the legacy fallback": This is by design. The feature is project-scoped Linear connections, and headlessLinearServices.test.ts ("does not share Linear credentials between headless projects") explicitly seeds a machine-level linear.token.v1 and asserts it must not bleed into a project. Adding a machine-store fallback/auto-migration would reintroduce exactly the cross-project credential bleed that test guards against. Trade-off: users who connected Linear on main (machine store) will reconnect per project after upgrading — that's the intended isolation boundary, not a regression. (Note: linear-token.v1.bin was always project-scoped, on main too, so the existing .bin readiness check is already correct.)
  • Greptile P2 — hasProjectCredentialStoreValue hardcodes credentials.json.enc / .machine-key: DEFAULT_CREDENTIALS_FILE / DEFAULT_MACHINE_KEY_FILE are module-private in credentialStore.ts; exporting internals to dedupe two filenames in a doctor probe isn't worth the coupling.
  • Greptile P2 — stale OAuth .bin on re-migration: the token and OAuth legacy files are cleaned on migration and on disconnect; the only residual case is a partial prior migration where the new store already holds the value, in which case the stale file is inert (the store takes precedence). Low risk, leaving as-is.
  • CodeRabbit — browserMock Linear state not persisted across calls: browserMock is Vite-preview-only with no production/test impact; making the connect/disconnect mock stateful is preview polish I'm deferring.

@arul28
Copy link
Copy Markdown
Owner Author

arul28 commented May 29, 2026

@codex review

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@arul28 arul28 merged commit 4863a52 into main May 29, 2026
28 checks passed
@arul28 arul28 deleted the ade/project-scoped-linear-connections-a45c3653 branch May 29, 2026 01:25
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