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
21 changes: 20 additions & 1 deletion src/codex/prompt-text-probe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,13 @@ export interface PromptTextProbe {
detail?: string;
}

// The prompt page is URL-addressable, so multiple mounts can ask for the same
// process-backed snapshot at once. Keep that work single-flight: callers share
// the current read instead of each consuming another child process and output
// buffer. The slot is cleared after either success or failure so later operator
// visits still observe current prompt state.
let activeProbe: Promise<PromptTextProbe> | null = null;

function resolveCodexBinary(): string | null {
const candidates = [
join(homedir(), ".codex/packages/standalone/current/bin/codex"),
Expand Down Expand Up @@ -182,7 +189,7 @@ export const extractSectionsForTests = extractSections;
* `cwd` matters: AGENTS.md and environment context are directory-dependent, so a
* probe from the wrong place would describe a prompt the user never sees.
*/
export async function probePromptText(timeoutMs = 15_000): Promise<PromptTextProbe> {
async function performPromptTextProbe(timeoutMs: number): Promise<PromptTextProbe> {
// The probe runs in CODEX_HOME, never in a caller-supplied directory. A `cwd`
// parameter let an authenticated request read any readable folder's AGENTS.md,
// and it also described a prompt that depends on where Codex happened to run.
Expand Down Expand Up @@ -236,3 +243,15 @@ export async function probePromptText(timeoutMs = 15_000): Promise<PromptTextPro
}
return { ok: true, codexHome, layers };
}

export async function probePromptText(timeoutMs = 15_000): Promise<PromptTextProbe> {
if (activeProbe) return activeProbe;

const probe = performPromptTextProbe(timeoutMs);
activeProbe = probe;
try {
return await probe;
} finally {
if (activeProbe === probe) activeProbe = null;
}
}
8 changes: 8 additions & 0 deletions tests/codex-prompt-route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -799,6 +799,14 @@ describe("020 coverage completions", () => {
// Decoding per chunk corrupts UTF-8 that straddles a chunk boundary.
expect(probe).toContain("Buffer.concat(chunks).toString(\"utf8\")");
});
test("27. concurrent prompt reads share one process-backed probe", async () => {
// The prompt panel is URL-addressable. Repeated mounts must not turn
// concurrent authenticated reads into concurrent Codex child processes.
const probe = await Bun.file(new URL("../src/codex/prompt-text-probe.ts", import.meta.url)).text();
expect(probe).toContain("let activeProbe: Promise<PromptTextProbe> | null = null;");
expect(probe).toContain("if (activeProbe) return activeProbe;");
expect(probe).toContain("if (activeProbe === probe) activeProbe = null;");
Comment on lines +805 to +808

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Test the single-flight behavior, not source substrings

This test never invokes concurrent probes; it only searches the implementation text and does not even assert the crucial activeProbe = probe assignment. Removing that assignment would restore one subprocess per caller while every expectation here still passes, so the reported regression is not actually guarded. Add a controllable spawn/probe seam, start simultaneous calls, assert one underlying invocation, and verify that a new invocation is allowed after settlement.

AGENTS.md reference: AGENTS.md:L284-L286

Useful? React with 👍 / 👎.

});
test("24. every ownership state is named, not collapsed into a boolean", async () => {
// developerInstructionsOwned:false covers an ABSENT key and an EXTERNAL one, and
// a GUI that cannot tell them apart hides its own create affordance from every
Expand Down
Loading