diff --git a/src/server/management/config-routes.ts b/src/server/management/config-routes.ts index 1bf09a673d..f837098e45 100644 --- a/src/server/management/config-routes.ts +++ b/src/server/management/config-routes.ts @@ -8,6 +8,7 @@ import { hasOwnProvider, isValidProviderName, multiAgentGuidanceEnabled, + mutatePersistedConfig, providerBaseUrlConfigError, providerHeadersConfigError, saveConfigPreservingClaudeCode, @@ -175,9 +176,28 @@ async function syncEnabledClientIntegrations( config.claudeCode?.desktopProfile, nativeContextLimits(config), ); - out.push(r.written - ? { client: "claude-desktop", ok: true, changed: true } - : { client: "claude-desktop", ok: false, reason: r.reason ?? "Claude Desktop write failed" }); + if (!r.written || !r.fingerprint) { + out.push({ client: "claude-desktop", ok: false, reason: r.reason ?? "Claude Desktop write failed" }); + } else { + const { emptyDesktopProfile } = await import("../../claude/desktop-profile"); + const marked = mutatePersistedConfig(persisted => { + const profile = persisted.claudeCode?.desktopProfile + ?? config.claudeCode?.desktopProfile + ?? emptyDesktopProfile(); + persisted.claudeCode = { + ...(persisted.claudeCode ?? {}), + desktopProfile: { + ...profile, + appliedFingerprint: r.fingerprint, + appliedAt: new Date().toISOString(), + }, + }; + return { changed: true, value: true }; + }); + out.push(marked.status === "unavailable" + ? { client: "claude-desktop", ok: false, reason: `Claude Desktop applied marker was not saved (${marked.reason})` } + : { client: "claude-desktop", ok: true, changed: true }); + } } catch (error) { out.push({ client: "claude-desktop", ok: false, reason: error instanceof Error ? error.message : String(error) }); } diff --git a/tests/sync-client-integrations.test.ts b/tests/sync-client-integrations.test.ts index 5c83efe696..b92f6339b4 100644 --- a/tests/sync-client-integrations.test.ts +++ b/tests/sync-client-integrations.test.ts @@ -51,11 +51,12 @@ describe("ocx sync fans out to the client integrations that are switched on", () // One catch per client: a broken Grok file is a warning, not a 500 on a command whose // main job (the Codex catalog) succeeded. expect(fn.match(/catch \(error\)/g)?.length).toBe(2); - // The Desktop write gets the native context limits, same as every other Desktop - // call site. 8b672205e threaded `nativeContextLimits` through those writers and - // left this assertion naming the retired `providerContextCap` spelling, so the - // source-shape check failed against the very change it is meant to pin. expect(fn).toContain("nativeContextLimits(config)"); + // Cleanup accepts only the fingerprint of the exact credential-bearing profile we wrote. + // Sync must durably advance that ownership marker rather than leaving the old value behind. + expect(fn).toContain("mutatePersistedConfig(persisted =>"); + expect(fn).toContain("appliedFingerprint: r.fingerprint"); + expect(fn.indexOf("writeDesktop3pConfig(")).toBeLessThan(fn.indexOf("appliedFingerprint: r.fingerprint")); // A client that is off is omitted rather than reported: the caller has to be able to // tell "left alone" from "tried and failed", so there is no skipped state to emit. expect(fn).not.toContain('"skipped"');