authorgravatar for admin@snappeh.comTristan Knight <admin@snappeh.com> 2026-09-26 20:56:11+01:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-26 14:56:11-05:00
logd6802b4acfbb6bfb669e0fa225539920622f7670
treec36e5257873a611a0464e0104e103d30ab7f5608
parent10bb59bf061252f5edadf96bc8139911b3fed7fd
signature Signed by PGP key B5690EEEBB952194

fix(usage): omit Cursor warning when no login is saved (#13820)


2 files changed, 43 insertions(+), 8 deletions(-)

apps/server/src/usage/UsageService.test.ts+41-8
......@@ -121,6 +121,46 @@ function totalOutputTokens(summary: { buckets: readonly { totals: { outputTokens
121121}
122122
123123describe("UsageService", () => {
124 it.live("omits Cursor account usage when no file login is saved", () =>
125 Effect.gen(function* () {
126 const { settings, home } = yield* setup;
127 for (const platform of ["linux", "win32", "darwin"] as const) {
128 const service = yield* UsageService.make.pipe(
129 Effect.provide(
130 serviceLayers({
131 prefix: `usage-service-cursor-no-login-${platform}`,
132 home,
133 settings,
134 platform,
135 environment: { AGENT_CLI_CREDENTIAL_STORE: "file" },
136 }),
137 ),
138 );
139 const summary = yield* service.readSummary(WINDOW);
140 assert.isFalse(summary.sources.some((source) => source.fingerprint.provider === "cursor"));
141 }
142 }).pipe(Effect.scoped),
143 );
144
145 it.live("keeps Cursor credential errors visible when a saved login cannot be read", () =>
146 Effect.gen(function* () {
147 const { settings, home } = yield* setup;
148 const authPath = NodePath.join(home, "config", "cursor", "auth.json");
149 yield* Effect.promise(async () => {
150 await NodeFSP.mkdir(NodePath.dirname(authPath), { recursive: true });
151 await NodeFSP.writeFile(authPath, "invalid json");
152 });
153 const service = yield* UsageService.make.pipe(
154 Effect.provide(
155 serviceLayers({ prefix: "usage-service-cursor-invalid-login", home, settings }),
156 ),
157 );
158 const summary = yield* service.readSummary(WINDOW);
159 const cursor = summary.sources.find((source) => source.fingerprint.provider === "cursor");
160 assert.strictEqual(cursor?.message, "Cursor credentials could not be read.");
161 }).pipe(Effect.scoped),
162 );
163
124164 it.live("does not read the macOS Cursor Keychain before account usage is enabled", () =>
125165 Effect.gen(function* () {
126166 const { settings, home } = yield* setup;
......@@ -233,10 +273,7 @@ describe("UsageService", () => {
233273 const summary = yield* service.readSummary(WINDOW);
234274 assert.strictEqual(summary.buckets[0]?.provider, "opencode");
235275 assert.isFalse(summary.buckets.some((bucket) => bucket.provider === "cursor"));
236 assert.strictEqual(
237 summary.sources.find((source) => source.fingerprint.provider === "cursor")?.status,
238 "missing",
239 );
276 assert.isFalse(summary.sources.some((source) => source.fingerprint.provider === "cursor"));
240277 assert.strictEqual(
241278 summary.buckets[0]?.sourcePath,
242279 yield* Effect.promise(() => NodeFSP.realpath(root)),
......@@ -247,10 +284,6 @@ describe("UsageService", () => {
247284 ?.distinctSessions,
248285 1,
249286 );
250 assert.include(
251 summary.sources.find((source) => source.fingerprint.provider === "cursor")?.message ?? "",
252 "Cursor account history needs a Cursor CLI login",
253 );
254287 }).pipe(Effect.scoped),
255288 );
256289
apps/server/src/usage/UsageService.ts+2
......@@ -635,6 +635,8 @@ export const make = Effect.gen(function* () {
635635 cursorUntilMs,
636636 ),
637637 );
638 // No saved login means there is no account source to report, not a setup error.
639 if (account.missing && account.error === null) return scanned;
638640 if (account.accountKey !== null && account.error === null && !account.missing) {
639641 // The same account includes CLI and desktop history from every machine.
640642 // A stable remote fingerprint prevents connected environments counting it twice.