From 0798f4e1173407a311d18417c8d78ed956ee4382 Mon Sep 17 00:00:00 2001 From: luvs01 Date: Mon, 31 Aug 2026 09:48:22 +0900 Subject: [PATCH 1/2] fix(claude): cache desktop policy status probes --- src/claude/desktop-policy.ts | 89 ++++++++++++++++++- .../management/agent-settings-routes.ts | 8 +- tests/claude-desktop-policy.test.ts | 39 ++++++++ 3 files changed, 131 insertions(+), 5 deletions(-) diff --git a/src/claude/desktop-policy.ts b/src/claude/desktop-policy.ts index 511b86e83c9..6c8b31b68eb 100644 --- a/src/claude/desktop-policy.ts +++ b/src/claude/desktop-policy.ts @@ -1,5 +1,5 @@ /** Read-only, privacy-safe Windows policy diagnosis for Claude Desktop 3P. */ -import { spawnSync } from "node:child_process"; +import { execFile, spawnSync } from "node:child_process"; import { win32 } from "node:path"; import { resolveTrustedWindowsSystemDirectory } from "../lib/windows-elevation"; import { decodeWindowsTextBytes } from "../lib/windows-text"; @@ -23,6 +23,11 @@ export type ClaudeDesktopPolicyProbeRunner = ( args: readonly string[], ) => ClaudeDesktopPolicyProbeResult; +export type ClaudeDesktopPolicyAsyncProbeRunner = ( + file: string, + args: readonly string[], +) => Promise; + export interface ClaudeDesktopPolicyProbeOptions { readonly platform?: NodeJS.Platform; readonly run?: ClaudeDesktopPolicyProbeRunner; @@ -54,6 +59,23 @@ const defaultPolicyProbeRunner: ClaudeDesktopPolicyProbeRunner = (file, args) => }; }; +const defaultAsyncPolicyProbeRunner: ClaudeDesktopPolicyAsyncProbeRunner = (file, args) => new Promise((resolve) => { + execFile(file, [...args], { + encoding: "buffer", + maxBuffer: 64 * 1024, + timeout: POLICY_PROBE_TIMEOUT_MS, + windowsHide: true, + }, (error, stdout) => { + const errorCode = (error as NodeJS.ErrnoException | null)?.code; + resolve({ + status: error === null ? 0 : typeof errorCode === "number" ? errorCode : null, + stdout: stdout ? decodeWindowsTextBytes(stdout) : "", + timedOut: errorCode === "ETIMEDOUT" || (error !== null && "killed" in error && error.killed === true), + spawnFailed: error !== null && typeof errorCode !== "number" && errorCode !== "ETIMEDOUT", + }); + }); +}); + function usable(result: ClaudeDesktopPolicyProbeResult): boolean { return !result.timedOut && !result.spawnFailed && result.status !== null; } @@ -108,6 +130,71 @@ export function probeClaudeDesktopPolicy( return parentListsPolicyKey(parent.stdout) ? "unknown" : "absent"; } +/** Non-blocking variant for the long-lived server request path. */ +export async function probeClaudeDesktopPolicyAsync( + options: Omit & { readonly run?: ClaudeDesktopPolicyAsyncProbeRunner } = {}, +): Promise { + const platform = options.platform ?? process.platform; + if (platform !== "win32") return "not_applicable"; + + let regExe: string; + try { + const systemDirectory = (options.resolveSystemDirectory ?? resolveTrustedWindowsSystemDirectory)(); + regExe = win32.join(systemDirectory, "reg.exe"); + } catch { + return "unknown"; + } + + const run = options.run ?? defaultAsyncPolicyProbeRunner; + try { + const policy = await run(regExe, ["query", CLAUDE_POLICY_KEY, "/reg:64"]); + if (!usable(policy)) return "unknown"; + if (policy.status === 0) return "present"; + if (policy.status !== 1) return "unknown"; + + const parent = await run(regExe, ["query", CLAUDE_POLICY_PARENT_KEY, "/reg:64"]); + if (!usable(parent) || parent.status !== 0) return "unknown"; + return parentListsPolicyKey(parent.stdout) ? "unknown" : "absent"; + } catch { + return "unknown"; + } +} + +const POLICY_CACHE_TTL_MS = 30_000; + +export function createCachedClaudeDesktopPolicyProbe( + probe: () => Promise, + ttlMs = POLICY_CACHE_TTL_MS, + now = Date.now, +): () => Promise { + let cached: { state: ClaudeDesktopPolicyState; expiresAt: number } | undefined; + let refresh: Promise | undefined; + return () => { + const currentTime = now(); + if (cached && cached.expiresAt > currentTime) return Promise.resolve(cached.state); + if (refresh) return refresh; + refresh = probe().then((state) => { + cached = { state, expiresAt: now() + ttlMs }; + return state; + }).finally(() => { + refresh = undefined; + }); + return refresh; + }; +} + +const cachedProductionProbe = createCachedClaudeDesktopPolicyProbe( + () => probeClaudeDesktopPolicyAsync(), +); + +/** Coalesces status polling and bounds registry refreshes to one per cache interval. */ +export function getCachedClaudeDesktopPolicy( + options: Omit = {}, +): Promise { + if (options.platform === undefined || options.platform === process.platform) return cachedProductionProbe(); + return probeClaudeDesktopPolicyAsync(options); +} + /** State-only health projection shared by CLI, apply, and management status. */ export function claudeDesktopPolicyHealth( state: ClaudeDesktopPolicyState, diff --git a/src/server/management/agent-settings-routes.ts b/src/server/management/agent-settings-routes.ts index d25e4895fbf..ed94ee97026 100644 --- a/src/server/management/agent-settings-routes.ts +++ b/src/server/management/agent-settings-routes.ts @@ -983,10 +983,10 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise // a stale apply the operator should refresh. const stale = desiredEnabled && observed.kind === "gateway_drifted"; const { getDesktopHealth } = await import("../../claude/desktop-health"); - const { claudeDesktopPolicyHealth, probeClaudeDesktopPolicy } = await import("../../claude/desktop-policy"); - const policyState = (deps.probeClaudeDesktopPolicy ?? probeClaudeDesktopPolicy)({ - platform: deps.platform ?? process.platform, - }); + const { claudeDesktopPolicyHealth, getCachedClaudeDesktopPolicy } = await import("../../claude/desktop-policy"); + const policyState = deps.probeClaudeDesktopPolicy + ? deps.probeClaudeDesktopPolicy({ platform: deps.platform ?? process.platform }) + : await getCachedClaudeDesktopPolicy({ platform: deps.platform ?? process.platform }); const policy = claudeDesktopPolicyHealth(policyState); const health = { ...getDesktopHealth(), diff --git a/tests/claude-desktop-policy.test.ts b/tests/claude-desktop-policy.test.ts index 648c3f16dd0..eae97a235fd 100644 --- a/tests/claude-desktop-policy.test.ts +++ b/tests/claude-desktop-policy.test.ts @@ -1,6 +1,8 @@ import { expect, test } from "bun:test"; import { claudeDesktopPolicyHealth, + createCachedClaudeDesktopPolicyProbe, + probeClaudeDesktopPolicyAsync, probeClaudeDesktopPolicy, type ClaudeDesktopPolicyProbeRunner, } from "../src/claude/desktop-policy"; @@ -122,3 +124,40 @@ test("non-Windows policy probing is not applicable and never spawns", () => { expect(spawned).toBe(false); expect(resolved).toBe(false); }); + +test("the asynchronous policy probe does not synchronously block the caller", async () => { + let release!: (value: ReturnType) => void; + const pending = new Promise>((resolve) => { release = resolve; }); + const probe = probeClaudeDesktopPolicyAsync({ + platform: "win32", + resolveSystemDirectory: () => "C:\\trusted\\System32", + run: () => pending, + }); + + let settled = false; + void probe.then(() => { settled = true; }); + await Promise.resolve(); + expect(settled).toBe(false); + release(result({ status: 0 })); + expect(await probe).toBe("present"); +}); + +test("status policy probes coalesce concurrent refreshes and cache the result", async () => { + let calls = 0; + let clock = 0; + let release!: (state: "present") => void; + const pending = new Promise<"present">((resolve) => { release = resolve; }); + const cachedProbe = createCachedClaudeDesktopPolicyProbe(async () => { + calls += 1; + return pending; + }, 30_000, () => clock); + + const first = cachedProbe(); + const concurrent = cachedProbe(); + expect(calls).toBe(1); + release("present"); + expect(await Promise.all([first, concurrent])).toEqual(["present", "present"]); + clock = 29_999; + expect(await cachedProbe()).toBe("present"); + expect(calls).toBe(1); +}); From be073abcb9cb539a010e7a3177678f7191d2ac78 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 21 Sep 2026 15:18:34 +0000 Subject: [PATCH 2/2] fix(claude): bound policy status cache by monotonic clock and honor custom resolver Co-Authored-By: Epinephrine --- src/claude/desktop-policy.ts | 32 +++++++---- tests/claude-desktop-policy.test.ts | 82 +++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 9 deletions(-) diff --git a/src/claude/desktop-policy.ts b/src/claude/desktop-policy.ts index 6c8b31b68eb..645e791901b 100644 --- a/src/claude/desktop-policy.ts +++ b/src/claude/desktop-policy.ts @@ -59,6 +59,24 @@ const defaultPolicyProbeRunner: ClaudeDesktopPolicyProbeRunner = (file, args) => }; }; +/** + * Translates an `execFile` callback into the probe contract. Exit codes arrive + * as numeric `error.code`; spawn failures carry a string errno; a timeout kill + * surfaces as `killed`/`SIGTERM` rather than `ETIMEDOUT`. + */ +export function classifyExecFileProbeResult( + error: (Error & { readonly code?: number | string; readonly killed?: boolean }) | null, + stdout: Uint8Array | undefined, +): ClaudeDesktopPolicyProbeResult { + const errorCode = error?.code; + return { + status: error === null ? 0 : typeof errorCode === "number" ? errorCode : null, + stdout: stdout === undefined ? "" : decodeWindowsTextBytes(stdout), + timedOut: errorCode === "ETIMEDOUT" || error?.killed === true, + spawnFailed: error !== null && typeof errorCode !== "number" && errorCode !== "ETIMEDOUT", + }; +} + const defaultAsyncPolicyProbeRunner: ClaudeDesktopPolicyAsyncProbeRunner = (file, args) => new Promise((resolve) => { execFile(file, [...args], { encoding: "buffer", @@ -66,13 +84,7 @@ const defaultAsyncPolicyProbeRunner: ClaudeDesktopPolicyAsyncProbeRunner = (file timeout: POLICY_PROBE_TIMEOUT_MS, windowsHide: true, }, (error, stdout) => { - const errorCode = (error as NodeJS.ErrnoException | null)?.code; - resolve({ - status: error === null ? 0 : typeof errorCode === "number" ? errorCode : null, - stdout: stdout ? decodeWindowsTextBytes(stdout) : "", - timedOut: errorCode === "ETIMEDOUT" || (error !== null && "killed" in error && error.killed === true), - spawnFailed: error !== null && typeof errorCode !== "number" && errorCode !== "ETIMEDOUT", - }); + resolve(classifyExecFileProbeResult(error, stdout)); }); }); @@ -165,7 +177,7 @@ const POLICY_CACHE_TTL_MS = 30_000; export function createCachedClaudeDesktopPolicyProbe( probe: () => Promise, ttlMs = POLICY_CACHE_TTL_MS, - now = Date.now, + now = performance.now, ): () => Promise { let cached: { state: ClaudeDesktopPolicyState; expiresAt: number } | undefined; let refresh: Promise | undefined; @@ -191,7 +203,9 @@ const cachedProductionProbe = createCachedClaudeDesktopPolicyProbe( export function getCachedClaudeDesktopPolicy( options: Omit = {}, ): Promise { - if (options.platform === undefined || options.platform === process.platform) return cachedProductionProbe(); + const cacheable = options.resolveSystemDirectory === undefined + && (options.platform === undefined || options.platform === process.platform); + if (cacheable) return cachedProductionProbe(); return probeClaudeDesktopPolicyAsync(options); } diff --git a/tests/claude-desktop-policy.test.ts b/tests/claude-desktop-policy.test.ts index eae97a235fd..58a60a06a13 100644 --- a/tests/claude-desktop-policy.test.ts +++ b/tests/claude-desktop-policy.test.ts @@ -1,7 +1,9 @@ import { expect, test } from "bun:test"; import { claudeDesktopPolicyHealth, + classifyExecFileProbeResult, createCachedClaudeDesktopPolicyProbe, + getCachedClaudeDesktopPolicy, probeClaudeDesktopPolicyAsync, probeClaudeDesktopPolicy, type ClaudeDesktopPolicyProbeRunner, @@ -142,6 +144,86 @@ test("the asynchronous policy probe does not synchronously block the caller", as expect(await probe).toBe("present"); }); +test("the execFile translation classifies exit codes, timeouts, and spawn failures", () => { + const probeError = (code: number | string, extra = {}) => + Object.assign(new Error("probe failed"), { code, ...extra }); + + expect(classifyExecFileProbeResult(null, Buffer.from("output"))).toEqual({ + status: 0, stdout: "output", timedOut: false, spawnFailed: false, + }); + // A missing key and a query failure surface as numeric exit codes, not errno names. + expect(classifyExecFileProbeResult(probeError(1), undefined)) + .toEqual({ status: 1, stdout: "", timedOut: false, spawnFailed: false }); + expect(classifyExecFileProbeResult(probeError(5), undefined)) + .toEqual({ status: 5, stdout: "", timedOut: false, spawnFailed: false }); + expect(classifyExecFileProbeResult(probeError("ENOENT"), undefined)) + .toEqual({ status: null, stdout: "", timedOut: false, spawnFailed: true }); + expect(classifyExecFileProbeResult(probeError("ETIMEDOUT"), undefined)) + .toEqual({ status: null, stdout: "", timedOut: true, spawnFailed: false }); + expect(classifyExecFileProbeResult(Object.assign(new Error("killed"), { killed: true }), undefined)) + .toEqual({ status: null, stdout: "", timedOut: true, spawnFailed: true }); +}); + +test("the asynchronous probe confirms a missing key through its readable parent", async () => { + const calls: string[] = []; + const state = await probeClaudeDesktopPolicyAsync({ + platform: "win32", + resolveSystemDirectory: () => "C:\\trusted\\System32", + run: (_file, args) => { + calls.push(args[1]!); + return Promise.resolve(args[1] === "HKLM\\SOFTWARE\\Policies" + ? result({ status: 0, stdout: "HKEY_LOCAL_MACHINE\\SOFTWARE\\Policies" }) + : result({ status: 1 })); + }, + }); + + expect(calls).toEqual(["HKLM\\SOFTWARE\\Policies\\Claude", "HKLM\\SOFTWARE\\Policies"]); + expect(state).toBe("absent"); +}); + +test("the asynchronous probe keeps an unreadable key unknown when its parent lists it", async () => { + const state = await probeClaudeDesktopPolicyAsync({ + platform: "win32", + resolveSystemDirectory: () => "C:\\trusted\\System32", + run: (_file, args) => Promise.resolve(args[1] === "HKLM\\SOFTWARE\\Policies" + ? result({ + status: 0, + stdout: [ + "HKEY_LOCAL_MACHINE\\SOFTWARE\\Policies", + " privatePolicyName REG_SZ private-value", + "HKEY_LOCAL_MACHINE\\SOFTWARE\\Policies\\Claude", + ].join("\r\n"), + }) + : result({ status: 1 })), + }); + + expect(state).toBe("unknown"); +}); + +test("the asynchronous probe reports unknown when the spawn itself fails", async () => { + const state = await probeClaudeDesktopPolicyAsync({ + platform: "win32", + resolveSystemDirectory: () => "C:\\trusted\\System32", + run: () => Promise.resolve(result({ status: null, spawnFailed: true })), + }); + + expect(state).toBe("unknown"); +}); + +test("cached status probes still honor a custom system-directory resolver", async () => { + let resolved = false; + const state = await getCachedClaudeDesktopPolicy({ + platform: "win32", + resolveSystemDirectory: () => { + resolved = true; + return "C:\\nonexistent-opencodex-test-dir"; + }, + }); + + expect(resolved).toBe(true); + expect(state).toBe("unknown"); +}); + test("status policy probes coalesce concurrent refreshes and cache the result", async () => { let calls = 0; let clock = 0;