Skip to content
Closed
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
18 changes: 15 additions & 3 deletions src/codex/app-server-processes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
resolveTrustedWindowsPowerShellExe,
resolveTrustedWindowsTaskkillExe,
} from "../lib/windows-elevation";
import { readCodexCatalogPath } from "./catalog/parsing";
import { activeCodexModelsCachePath, readCodexCatalogPath } from "./catalog/parsing";

export const STALE_CODEX_APP_SERVER_HINT =
"If Codex still shows an older model list, run `ocx sync --restart-codex`: it restarts the long-lived app-server "
Expand Down Expand Up @@ -141,6 +141,7 @@ export interface CodexAppServerProcessIo {
/** Async batch start-time seam used by the request-path Windows collector. */
readStartMsBatchAsync?: (pids: readonly number[]) => Promise<Map<number, number | null>>;
catalogMtimeMs?: () => number | null;
modelsCacheMtimeMs?: () => number | null;
}

function execFileTextAsync(
Expand Down Expand Up @@ -754,6 +755,10 @@ function defaultCatalogMtimeMs(): number | null {
}
}

function defaultModelsCacheMtimeMs(): number | null {
try { return statSync(activeCodexModelsCachePath()).mtimeMs; } catch { return null; }
}

function codexAppServerProcessesFromSnapshots(
snapshots: readonly ProcessSnapshot[],
): CodexAppServerProcess[] {
Expand Down Expand Up @@ -1254,12 +1259,19 @@ export function afterCatalogWriteHandleAppServers(
const observed = computeCodexAppServerCatalogStatus(options.io ?? {});
const starts = new Map(observed.status.processes.map(process => [process.pid, process.startedAtMs]));
const catalogMtimeMs = observed.status.catalogMtimeMs;
// Injected catalog clocks are self-contained test/probe observations. Production
// also includes cache-only writes, whose mtime can be newer than the catalog.
const cacheMtimeMs = options.io?.modelsCacheMtimeMs
? options.io.modelsCacheMtimeMs()
: options.io?.catalogMtimeMs ? null : defaultModelsCacheMtimeMs();
const writeMtimeMs = catalogMtimeMs === null ? cacheMtimeMs
: cacheMtimeMs === null ? catalogMtimeMs : Math.max(catalogMtimeMs, cacheMtimeMs);
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
const processes = observed.processes.filter(process => !excluded.has(process.pid));
const staleProcesses = catalogMtimeMs === null
const staleProcesses = writeMtimeMs === null
? []
: processes.filter(process => {
const startedAtMs = starts.get(process.pid);
return startedAtMs !== null && startedAtMs !== undefined && startedAtMs <= catalogMtimeMs;
return startedAtMs !== null && startedAtMs !== undefined && startedAtMs <= writeMtimeMs;
});
if (staleProcesses.length === 0) return { processes, warned: false, hint };
options.log?.error(formatStaleCodexAppServerWarning(staleProcesses));
Expand Down
2 changes: 1 addition & 1 deletion structure/runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ Catalog-derived reasoning-level diagnostics are escaped only at the human-output
`ocx system codex-restart` requests a full Codex desktop-app restart and app-server restarts through the management endpoint. `src/cli/capabilities.ts` names that scope in its summary and `--yes` description; `src/cli/system-command.ts` explains the desktop interruption when confirmation is missing and sends no restart request. Human output says the restart was requested, while `--json` preserves the complete server result, including skipped or refused desktop outcomes.

After a CLI catalog/cache write, advisory restart guidance compares each running Codex app-server's
start time with the written catalog mtime. It reports only processes proven stale; a fresh or
start time with the newest catalog or models-cache mtime. It reports only processes proven stale; a fresh or
unreadable observation does not claim that another restart is required. Explicit
`--restart-codex` and `--restart-app-server-only` retain their operator-consent semantics and act on
Comment on lines 67 to 70

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Public stale-state documentation remains catalog-only

The public sub-agent guide defines freshness only against the catalog. If cache writes belong to the global contract, update that page and its translations.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

verified matching processes regardless of the advisory freshness result.
Expand Down
6 changes: 3 additions & 3 deletions tests/codex-integration/codex-app-server-processes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -716,13 +716,13 @@ describe("Codex app-server process matching (#476)", () => {
listSnapshots: () => snapshots,
readStartMs: pid => pid === 7 ? 1_000 : 3_000,
catalogMtimeMs: () => 2_000,
modelsCacheMtimeMs: () => 4_000,
},
});
expect(result.warned).toBe(true);
expect(result.processes.map(process => process.pid)).toEqual([7]);
expect(result.processes.map(process => process.pid)).toEqual([7, 8]);
expect(errors).toHaveLength(1);
expect(errors[0]).toContain("PID: 7");
expect(errors[0]).not.toContain("8");
expect(errors[0]).toContain("PIDs: 7, 8");
});

test("afterCatalogWriteHandleAppServers stays quiet for fresh and unknown observations", () => {
Expand Down
Loading