Skip to content
Draft
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
19 changes: 16 additions & 3 deletions src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,7 @@ function inspectStartupOwnership(
currentHomes: ReturnType<typeof currentServiceHomes> | null,
statePaths: readonly string[] | null,
windowsTaskListingCache?: ReturnType<typeof createWindowsTaskListingCache>,
skipWindowsTaskListing = false,
): OwnershipInspection {
try {
if (currentHomes === null || statePaths === null) {
Expand All @@ -510,9 +511,19 @@ function inspectStartupOwnership(
};
}
if (deps.inspectNativeCodexOwnership) {
return deps.inspectNativeCodexOwnership({ currentHomes, statePaths, windowsTaskListingCache });
return deps.inspectNativeCodexOwnership({
currentHomes,
statePaths,
windowsTaskListingCache,
skipWindowsTaskListing,
});
}
return inspectNativeCodexOwnership({ currentHomes, statePaths, windowsTaskListingCache });
return inspectNativeCodexOwnership({
currentHomes,
statePaths,
windowsTaskListingCache,
skipWindowsTaskListing,
});
} catch {
return {
ownership: "unknown",
Expand Down Expand Up @@ -825,7 +836,9 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
}
const homes = retryOwnershipHomes;
const statePaths = retryOwnershipStatePaths;
const answer = inspectStartupOwnership(deps, homes, statePaths).ownership;
// Admission checks run on Bun's event loop. Keep their recovery probe to
// targeted queries; the potentially 20-second full listing is startup-only.
const answer = inspectStartupOwnership(deps, homes, statePaths, undefined, true).ownership;
if (answer !== "owned") return answer;
retryPreparedNativeMainLifecycle ??= prepareNativeMainStartupLifecycle(
deps.nativeMainStartup,
Expand Down
7 changes: 6 additions & 1 deletion src/service-manager-probe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@ export interface ProbeDeps {
readonly windowsLocale?: string;
/** Startup-local full-listing cache; targeted task queries always bypass it. */
readonly windowsTaskListingCache?: WindowsTaskListingCache;
/** Do not enumerate every scheduled task from latency-sensitive callers. */
readonly skipWindowsTaskListing?: boolean;
}

const LABEL = "com.opencodex.proxy";
Expand Down Expand Up @@ -633,7 +635,7 @@ const SCHTASKS_TASK_NOT_FOUND_EN = /cannot find the file specified/i;
*/
function probeWindowsTaskRegistration(
deps: Required<Pick<ProbeDeps, "runRaw">>
& Pick<ProbeDeps, "windowsLocale" | "windowsTaskListingCache">,
& Pick<ProbeDeps, "windowsLocale" | "windowsTaskListingCache" | "skipWindowsTaskListing">,
): {
registered: "present" | "absent" | "unknown";
registeredXml: string;
Expand All @@ -659,6 +661,9 @@ function probeWindowsTaskRegistration(
if (queried.status !== null && SCHTASKS_TASK_NOT_FOUND_EN.test(queryText)) {
return { registered: "absent", registeredXml: "" };
}
if (deps.skipWindowsTaskListing) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Forward the skip flag into the Windows probe

When a demand-driven ownership retry reaches Windows after a targeted schtasks query fails with a non-English or otherwise inconclusive response, this guard never fires: inspectServiceManagerInstallation reconstructs the argument to inspectWindows at lines 993–1000 but omits deps.skipWindowsTaskListing. Consequently the request path still executes the full scheduled-task listing and can block Bun's event loop for its 20-second timeout. Add the flag to inspectWindows's dependency type and forward it from the Windows dispatch.

Useful? React with 👍 / 👎.

return { registered: "unknown", registeredXml: "" };
}

const runListing = () => deps.runRaw(
schtasks,
Expand Down
23 changes: 23 additions & 0 deletions tests/codex-service-manager-probe-hardening.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,29 @@
expect(result.kind).toBe("unknown");
});

test("a latency-sensitive ownership probe does not enumerate scheduled tasks", () => {
let fullListings = 0;
const result = inspectServiceManagerInstallation({
platform: "win32",
home,
configDir,
windowsLocale: "zh-CN",
skipWindowsTaskListing: true,
runRaw: (file, args) => {
if (file.toLowerCase().endsWith("sc.exe")) return raw(1, "", "1060");
if (args.includes("/xml")) {
return { status: 1, stdout: Buffer.alloc(0), stderr: GBK_TASK_NOT_FOUND, timedOut: false, spawnFailed: false };
}
if (args.includes("/fo")) fullListings += 1;
return raw(0, "");
},
winswStatus: () => "nonexistent",
});

expect(result.kind).toBe("unknown");

Check failure on line 234 in tests/codex-service-manager-probe-hardening.test.ts

View workflow job for this annotation

GitHub Actions / macos

error: expect(received).toBe(expected)

Expected: "unknown" Received: "absent" at <anonymous> (/Users/runner/work/opencodex/opencodex/tests/codex-service-manager-probe-hardening.test.ts:234:25)

Check failure on line 234 in tests/codex-service-manager-probe-hardening.test.ts

View workflow job for this annotation

GitHub Actions / test 3/4

error: expect(received).toBe(expected)

Expected: "unknown" Received: "absent" at <anonymous> (/home/runner/work/opencodex/opencodex/tests/codex-service-manager-probe-hardening.test.ts:234:25)
expect(fullListings).toBe(0);
});

test("one startup keeps two targeted queries but shares one unchanged full listing (#2923)", async () => {
const codexHome = join(home, "codex");
mkdirSync(codexHome, { recursive: true });
Expand Down
9 changes: 8 additions & 1 deletion tests/native-profile-startup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -759,12 +759,14 @@ describe("an unknown service-ownership fence is retryable (#2108)", () => {
const scopes: Array<{
currentHomes?: { codexHome: string; opencodexHome: string };
statePaths?: readonly string[];
skipWindowsTaskListing?: boolean;
}> = [];
const server = startServer(0, {
inspectNativeCodexOwnership: (scope = {}) => {
scopes.push({
currentHomes: scope.currentHomes ? { ...scope.currentHomes } : undefined,
statePaths: scope.statePaths ? [...scope.statePaths] : undefined,
skipWindowsTaskListing: scope.skipWindowsTaskListing,
});
return { ownership: answer, reason: "pinned startup test" };
},
Expand All @@ -791,7 +793,12 @@ describe("an unknown service-ownership fence is retryable (#2108)", () => {
opencodexHome: f.configDir,
});
expect(firstScope.statePaths?.[0]).toBe(join(f.configDir, "service-state.json"));
for (const scope of scopes.slice(1)) expect(scope).toEqual(firstScope);
expect(scopes.slice(0, 2).every(scope => scope.skipWindowsTaskListing === false)).toBe(true);
expect(scopes.slice(2).every(scope => scope.skipWindowsTaskListing === true)).toBe(true);
for (const scope of scopes.slice(1)) {
expect(scope.currentHomes).toEqual(firstScope.currentHomes);
expect(scope.statePaths).toEqual(firstScope.statePaths);
}

finishRecovery();
expect(await waitForNativeMainStartupGate()).toEqual({
Expand Down
Loading