Skip to content
Open
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
14 changes: 13 additions & 1 deletion src/server/responses/collaboration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,10 +249,22 @@ export async function multiAgentGuidanceText(
// Codex cannot actually spawn makes spawn_agent reject the override, so
// suppress positive model claims while the state is stale or unknown.
const catalogState = await (deps.collectCatalogState ?? defaultCollectCatalogState)();
if (catalogState.state === "stale" || catalogState.state === "unknown") {
if (catalogState.state === "stale") {
return "<multi_agent_mode>The model catalog changed after Codex started; do not set "
+ "model or reasoning_effort overrides until Codex restarts.</multi_agent_mode>";
}
// #1354: `unknown` is not `stale`. It means the comparison could not be made
// — the app-server start time or the catalog mtime was unreadable (a
// transient code-mode-host process exiting between enumeration and the
// start-time read is one way to land here). Suppressing overrides is still
// correct, but the stale text asserts a cause that was never established and
// prescribes a restart that cannot clear `unknown`. Mirror the honest
// phrasing `ocx doctor` already uses for this state.
if (catalogState.state === "unknown") {
return "<multi_agent_mode>Could not verify whether Codex's model catalog is current "
+ "(app-server start time or catalog timestamp unreadable); do not set model or "
+ "reasoning_effort overrides for this turn.</multi_agent_mode>";
}
// codex-rs supplies the Proactive text on v2; the proxy only adds model-designation
// guidance, and only when there is something concrete to designate: a configured
// injectionModel and/or a roster entry that resolves in the injected catalog.
Expand Down
41 changes: 41 additions & 0 deletions tests/multi-agent-compat.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,47 @@ describe("multiAgentGuidanceText", () => {
}
});

test("v2 guidance does not assert a catalog change or a restart remedy when the state is unknown (#1354)", async () => {
const dir = codexHomeFixture(V2_ON);
catalogFixture(dir, [{
slug: "anthropic/claude-sonnet-5",
efforts: ["low", "medium", "high", "xhigh"],
}]);
const parsed = parsedFixture({ reasoning: "medium", tools: [{ name: "spawn_agent" }] });
const options = { injectionModel: "anthropic/claude-sonnet-5" };

const stale = await multiAgentGuidanceText(parsed, options, {
collectCatalogState: () => ({ state: "stale" }),
});
const unknown = await multiAgentGuidanceText(parsed, options, {
collectCatalogState: () => ({ state: "unknown" }),
});

// "stale" is an established fact: the catalog really did change after the
// app-server started, and restarting Codex really does clear it.
expect(stale).toContain("The model catalog changed after Codex started");
expect(stale).toContain("until Codex restarts");

// "unknown" means the comparison could not be made at all — the process
// start time or the catalog mtime was unreadable. Neither the cause nor the
// remedy is established, and a restart cannot clear it, so the guidance must
// not assert either.
expect(unknown).not.toContain("The model catalog changed after Codex started");
expect(unknown).not.toContain("until Codex restarts");
expect(unknown).toContain("Could not verify");

// The suppression itself is correct in both states and must not regress:
// withhold the override instruction and any positive model claims.
for (const text of [stale, unknown]) {
expect(text).toContain("do not set");
expect(text).not.toContain("Preferred sub-agent");
expect(text).not.toContain("Available models");
}

// The two states must stay distinguishable to an operator reading the turn.
expect(unknown).not.toBe(stale);
});

test("v2 built-in guidance is schema-agnostic and keeps fork rules", async () => {
const dir = codexHomeFixture(V2_ON);
catalogFixture(dir, [{
Expand Down
Loading