Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/ade-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ ADE CLI auth is local project access, not a separate cloud login. `ade auth stat
- Machine socket path, whether the socket exists, and whether this invocation is using `runtime-socket`, `desktop-socket`, or `headless` mode.
- RPC tool count, ADE action count, and action counts by domain.
- Git repository readiness and GitHub readiness signals from local remotes, `gh` availability, and token environment presence.
- Linear readiness from local encrypted token presence or headless environment variables.
- Linear readiness from the active project's `.ade/secrets` credential store (`linear.token.v1`), a legacy project-scoped encrypted token file, or headless environment variables.
- Provider/model readiness from local ADE config, API-key provider references, and provider CLI availability.
- Computer-use readiness from local platform capabilities.
- Packaged/PATH status for the `ade` binary and concrete next actions.
Expand Down
37 changes: 37 additions & 0 deletions apps/ade-cli/src/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { describe, expect, it } from "vitest";
import {
buildAdeCodeArgs,
buildCliPlan,
checkLinearReadiness,
findProjectRoots,
formatOutput,
graphWaitState,
Expand All @@ -19,6 +20,7 @@ import {
summarizeExecution,
unwrapToolResult,
} from "./cli";
import { EncryptedFileCredentialStore } from "./services/credentials/credentialStore";

type ResolveRootsOptions = Parameters<typeof resolveRoots>[0];

Expand Down Expand Up @@ -991,6 +993,41 @@ describe("ADE CLI", () => {
expect(output).toContain("Git repository detected");
});

it("detects project-local Linear credentials in doctor readiness", () => {
const previousAdeLinearApi = process.env.ADE_LINEAR_API;
const previousLinearApiKey = process.env.LINEAR_API_KEY;
const previousAdeLinearToken = process.env.ADE_LINEAR_TOKEN;
const previousLinearToken = process.env.LINEAR_TOKEN;
delete process.env.ADE_LINEAR_API;
delete process.env.LINEAR_API_KEY;
delete process.env.ADE_LINEAR_TOKEN;
delete process.env.LINEAR_TOKEN;
const projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), "ade-cli-linear-readiness-"));
const store = new EncryptedFileCredentialStore({
secretsDir: path.join(projectRoot, ".ade", "secrets"),
});
store.setSync("linear.token.v1", "lin_project_token");

try {
const readiness = checkLinearReadiness(projectRoot);
expect(readiness.ready).toBe(true);
expect(readiness.details).toMatchObject({
projectCredentialStoreTokenPresent: true,
tokenEnvPresent: false,
});
} finally {
fs.rmSync(projectRoot, { recursive: true, force: true });
if (previousAdeLinearApi === undefined) delete process.env.ADE_LINEAR_API;
else process.env.ADE_LINEAR_API = previousAdeLinearApi;
if (previousLinearApiKey === undefined) delete process.env.LINEAR_API_KEY;
else process.env.LINEAR_API_KEY = previousLinearApiKey;
if (previousAdeLinearToken === undefined) delete process.env.ADE_LINEAR_TOKEN;
else process.env.ADE_LINEAR_TOKEN = previousAdeLinearToken;
if (previousLinearToken === undefined) delete process.env.LINEAR_TOKEN;
else process.env.LINEAR_TOKEN = previousLinearToken;
}
Comment thread
greptile-apps[bot] marked this conversation as resolved.
});

it("attempts Windows named-pipe desktop sockets without filesystem existence checks", () => {
expect(shouldAttemptDesktopSocketConnection("\\\\.\\pipe\\ade-123")).toBe(
true,
Expand Down
38 changes: 35 additions & 3 deletions apps/ade-cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import { MACOS_VM_PHASES } from "../../desktop/src/shared/types/macosVm";
import type { AdeServiceCommand } from "./serviceManager/common";
import { normalizeAdeRuntimeRole, resolveAdeDefaultRole } from "./runtimeRoles";
import type { AdeRuntime } from "./bootstrap";
import { EncryptedFileCredentialStore } from "./services/credentials/credentialStore";

type JsonObject = Record<string, unknown>;

Expand Down Expand Up @@ -9057,16 +9058,23 @@ function checkGitHubReadiness(projectRoot: string): ReadinessCheck {
function checkLinearReadiness(projectRoot: string): ReadinessCheck {
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",
);
Comment on lines 9059 to +9067
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".

const envTokenPresent = Boolean(
process.env.ADE_LINEAR_API?.trim() ||
process.env.LINEAR_API_KEY?.trim() ||
process.env.ADE_LINEAR_TOKEN?.trim() ||
process.env.LINEAR_TOKEN?.trim(),
);
const ready = encryptedTokenPresent || envTokenPresent;
const ready =
legacyEncryptedTokenPresent ||
projectCredentialStoreTokenPresent ||
envTokenPresent;
return {
ready,
status: ready ? "ready" : "warning",
Expand All @@ -9077,12 +9085,35 @@ function checkLinearReadiness(projectRoot: string): ReadinessCheck {
? undefined
: "Configure Linear in ADE desktop or set ADE_LINEAR_API/LINEAR_API_KEY for headless mode.",
details: {
encryptedTokenPresent,
encryptedTokenPresent:
legacyEncryptedTokenPresent || projectCredentialStoreTokenPresent,
legacyEncryptedTokenPresent,
projectCredentialStoreTokenPresent,
tokenEnvPresent: envTokenPresent,
},
};
}

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;
}
}
Comment on lines +9097 to +9115
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


function checkProviderReadiness(value: unknown): ReadinessCheck {
const configResult =
isRecord(value) && isRecord(value.result) ? value.result : value;
Expand Down Expand Up @@ -13323,6 +13354,7 @@ if (/(^|[/\\])cli\.(?:ts|js|cjs)$/.test(process.argv[1] ?? "")) {
export {
buildCliPlan,
buildAdeCodeArgs,
checkLinearReadiness,
findProjectRoots,
formatOutput,
graphWaitState,
Expand Down
123 changes: 99 additions & 24 deletions apps/ade-cli/src/headlessLinearServices.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,25 +106,27 @@ vi.mock("../../desktop/src/main/services/automations/automationSecretService", (
import { EncryptedFileCredentialStore } from "./services/credentials/credentialStore";
import { createHeadlessGitHubService, createHeadlessLinearServices } from "./headlessLinearServices";

function createDeps() {
function createDeps(overrides: Record<string, any> = {}) {
const projectRoot = overrides.projectRoot ?? "/tmp/ade-project";
const adeDir = overrides.adeDir ?? path.join(projectRoot, ".ade");
return {
projectRoot: "/tmp/ade-project",
adeDir: "/tmp/ade-project/.ade",
projectRoot,
adeDir,
paths: {
adeDir: "/tmp/ade-project/.ade",
logsDir: "/tmp/ade-project/.ade/logs",
processLogsDir: "/tmp/ade-project/.ade/logs/processes",
testLogsDir: "/tmp/ade-project/.ade/logs/tests",
transcriptsDir: "/tmp/ade-project/.ade/transcripts",
worktreesDir: "/tmp/ade-project/.ade/worktrees",
packsDir: "/tmp/ade-project/.ade/packs",
dbPath: "/tmp/ade-project/.ade/ade.db",
socketPath: "/tmp/ade-project/.ade/ade.sock",
cacheDir: "/tmp/ade-project/.ade/cache",
artifactsDir: "/tmp/ade-project/.ade/artifacts",
chatSessionsDir: "/tmp/ade-project/.ade/chats/sessions",
chatTranscriptsDir: "/tmp/ade-project/.ade/chats/transcripts",
orchestratorCacheDir: "/tmp/ade-project/.ade/cache/orchestrator",
adeDir,
logsDir: path.join(adeDir, "logs"),
processLogsDir: path.join(adeDir, "logs", "processes"),
testLogsDir: path.join(adeDir, "logs", "tests"),
transcriptsDir: path.join(adeDir, "transcripts"),
worktreesDir: path.join(adeDir, "worktrees"),
packsDir: path.join(adeDir, "packs"),
dbPath: path.join(adeDir, "ade.db"),
socketPath: path.join(adeDir, "ade.sock"),
cacheDir: path.join(adeDir, "cache"),
artifactsDir: path.join(adeDir, "artifacts"),
chatSessionsDir: path.join(adeDir, "chats", "sessions"),
chatTranscriptsDir: path.join(adeDir, "chats", "transcripts"),
orchestratorCacheDir: path.join(adeDir, "cache", "orchestrator"),
},
projectId: "project-1",
db: {} as any,
Expand All @@ -137,6 +139,7 @@ function createDeps() {
workerBudgetService: {} as any,
computerUseArtifactBrokerService: {} as any,
openExternal: async () => {},
...overrides,
};
}

Expand Down Expand Up @@ -503,18 +506,31 @@ describe("headlessLinearServices", () => {
}
});

it("reads Linear and GitHub credentials from the shared machine store", () => {
it("reads Linear credentials from the project store and GitHub credentials from the shared machine store", () => {
const previousAdeHome = process.env.ADE_HOME;
const previousAdeLinearApi = process.env.ADE_LINEAR_API;
const previousLinearApiKey = process.env.LINEAR_API_KEY;
const previousAdeLinearToken = process.env.ADE_LINEAR_TOKEN;
const previousLinearToken = process.env.LINEAR_TOKEN;
process.env.ADE_HOME = fs.mkdtempSync(path.join(os.tmpdir(), "ade-headless-shared-credentials-"));
const credentialStore = new EncryptedFileCredentialStore();
credentialStore.setSync("linear.token.v1", "lin_shared_token");
credentialStore.setSync("github.token.v1", "ghp_shared_token");
delete process.env.ADE_LINEAR_API;
delete process.env.LINEAR_API_KEY;
delete process.env.ADE_LINEAR_TOKEN;
delete process.env.LINEAR_TOKEN;
const projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), "ade-headless-linear-project-"));
const adeDir = path.join(projectRoot, ".ade");
const projectCredentialStore = new EncryptedFileCredentialStore({
secretsDir: path.join(adeDir, "secrets"),
});
projectCredentialStore.setSync("linear.token.v1", "lin_project_token");
const machineCredentialStore = new EncryptedFileCredentialStore();
machineCredentialStore.setSync("github.token.v1", "ghp_shared_token");

const services = createHeadlessLinearServices(createDeps());
const services = createHeadlessLinearServices(createDeps({ projectRoot, adeDir }));
try {
expect(services.linearCredentialService.getTokenOrThrow()).toBe("lin_shared_token");
expect(services.linearCredentialService.getTokenOrThrow()).toBe("lin_project_token");
const githubService = createHeadlessGitHubService(
"/tmp/ade-project",
projectRoot,
{ debug() {}, info() {}, warn() {}, error() {} } as any,
);
expect(githubService.getTokenOrThrow()).toBe("ghp_shared_token");
Expand All @@ -525,6 +541,65 @@ describe("headlessLinearServices", () => {
} else {
process.env.ADE_HOME = previousAdeHome;
}
if (previousAdeLinearApi == null) delete process.env.ADE_LINEAR_API;
else process.env.ADE_LINEAR_API = previousAdeLinearApi;
if (previousLinearApiKey == null) delete process.env.LINEAR_API_KEY;
else process.env.LINEAR_API_KEY = previousLinearApiKey;
if (previousAdeLinearToken == null) delete process.env.ADE_LINEAR_TOKEN;
else process.env.ADE_LINEAR_TOKEN = previousAdeLinearToken;
if (previousLinearToken == null) delete process.env.LINEAR_TOKEN;
else process.env.LINEAR_TOKEN = previousLinearToken;
}
});

it("does not share Linear credentials between headless projects", () => {
const previousAdeHome = process.env.ADE_HOME;
const previousAdeLinearApi = process.env.ADE_LINEAR_API;
const previousLinearApiKey = process.env.LINEAR_API_KEY;
const previousAdeLinearToken = process.env.ADE_LINEAR_TOKEN;
const previousLinearToken = process.env.LINEAR_TOKEN;
process.env.ADE_HOME = fs.mkdtempSync(path.join(os.tmpdir(), "ade-headless-linear-isolation-"));
delete process.env.ADE_LINEAR_API;
delete process.env.LINEAR_API_KEY;
delete process.env.ADE_LINEAR_TOKEN;
delete process.env.LINEAR_TOKEN;
const projectOneRoot = fs.mkdtempSync(path.join(os.tmpdir(), "ade-linear-project-one-"));
const projectTwoRoot = fs.mkdtempSync(path.join(os.tmpdir(), "ade-linear-project-two-"));
const projectOneAdeDir = path.join(projectOneRoot, ".ade");
const projectTwoAdeDir = path.join(projectTwoRoot, ".ade");

const projectOneCredentials = new EncryptedFileCredentialStore({
secretsDir: path.join(projectOneAdeDir, "secrets"),
});
projectOneCredentials.setSync("linear.token.v1", "lin_project_one");
const machineCredentials = new EncryptedFileCredentialStore();
machineCredentials.setSync("linear.token.v1", "lin_machine_should_not_bleed");

const projectOne = createHeadlessLinearServices(createDeps({
projectRoot: projectOneRoot,
adeDir: projectOneAdeDir,
}));
const projectTwo = createHeadlessLinearServices(createDeps({
projectRoot: projectTwoRoot,
adeDir: projectTwoAdeDir,
}));
try {
expect(projectOne.linearCredentialService.getTokenOrThrow()).toBe("lin_project_one");
expect(projectTwo.linearCredentialService.getStatus().tokenStored).toBe(false);
expect(() => projectTwo.linearCredentialService.getTokenOrThrow()).toThrow("Linear token missing");
} finally {
projectOne.dispose();
projectTwo.dispose();
if (previousAdeHome == null) delete process.env.ADE_HOME;
else process.env.ADE_HOME = previousAdeHome;
if (previousAdeLinearApi == null) delete process.env.ADE_LINEAR_API;
else process.env.ADE_LINEAR_API = previousAdeLinearApi;
if (previousLinearApiKey == null) delete process.env.LINEAR_API_KEY;
else process.env.LINEAR_API_KEY = previousLinearApiKey;
if (previousAdeLinearToken == null) delete process.env.ADE_LINEAR_TOKEN;
else process.env.ADE_LINEAR_TOKEN = previousAdeLinearToken;
if (previousLinearToken == null) delete process.env.LINEAR_TOKEN;
else process.env.LINEAR_TOKEN = previousLinearToken;
}
});

Expand Down
11 changes: 8 additions & 3 deletions apps/ade-cli/src/headlessLinearServices.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { randomUUID } from "node:crypto";
import { spawnSync } from "node:child_process";
import path from "node:path";
import type { Logger } from "../../desktop/src/main/services/logging/logger";
import type { AdeDb } from "../../desktop/src/main/services/state/kvDb";
import type { createLaneService } from "../../desktop/src/main/services/lanes/laneService";
Expand Down Expand Up @@ -1135,8 +1136,12 @@ export function createHeadlessGitHubService(
return service;
}

function createHeadlessLinearCredentialService(): HeadlessLinearCredentialService {
const credentialStore = new EncryptedFileCredentialStore();
function createHeadlessLinearCredentialService(args: {
adeDir: string;
}): HeadlessLinearCredentialService {
const credentialStore = new EncryptedFileCredentialStore({
secretsDir: path.join(args.adeDir, "secrets"),
});
const tokenKey = "linear.token.v1";
const authModeKey = "linear.authMode.v1";
const tokenExpiresAtKey = "linear.tokenExpiresAt.v1";
Expand Down Expand Up @@ -1638,7 +1643,7 @@ export function createHeadlessLinearServices(
logger: args.logger,
});
const linearCredentialService =
createHeadlessLinearCredentialService() as any;
createHeadlessLinearCredentialService({ adeDir: args.adeDir }) as any;
const githubService = createHeadlessGitHubService(
args.projectRoot,
args.logger,
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/main/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2704,7 +2704,7 @@ app.whenReady().then(async () => {
adeDir: adePaths.adeDir,
logger,
credentialStore: new EncryptedFileCredentialStore({
secretsDir: machineAdeLayout.secretsDir,
secretsDir: path.join(adePaths.adeDir, "secrets"),
}),
});
const linearClient = createLinearClient({
Expand Down
4 changes: 3 additions & 1 deletion apps/desktop/src/main/services/cto/linearAuth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ describe("linearCredentialService", () => {
expect(fs.readFileSync(sentinelPath, "utf8")).toContain("imported");
});

it("migrates project-local Linear credentials into the machine credential store once", () => {
it("migrates legacy project-local Linear credentials into the injected credential store once", () => {
const previousAdeLinearApi = process.env.ADE_LINEAR_API;
const previousLinearApiKey = process.env.LINEAR_API_KEY;
const previousAdeLinearToken = process.env.ADE_LINEAR_TOKEN;
Expand Down Expand Up @@ -200,6 +200,8 @@ describe("linearCredentialService", () => {
clientId: "client-project",
clientSecret: "secret-project",
});
expect(fs.existsSync(path.join(secretsDir, "linear-token.v1.bin"))).toBe(false);
expect(fs.existsSync(path.join(secretsDir, "linear-oauth-client.v1.bin"))).toBe(false);

service.clearToken();
const reloaded = createLinearCredentialService({
Expand Down
Loading
Loading