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
20 changes: 14 additions & 6 deletions src/lib/shadow-call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,23 @@ export function isShadowSourceModel(modelId: string, configured?: unknown): bool
/**
* Decide whether a matching source model should use the opt-in intercept.
*
* Before Codex 0.147.0 this checked x-codex-turn-metadata and exempted
* request_kind "turn". Codex 0.147.0 can label background helper calls as
* "turn", causing them to bypass the intercept (#1684). The fix is to
* intercept every configured shadow source model unconditionally — the model
* slug alone is a sufficient signal.
* Codex identifies normal user turns and maintenance requests in
* x-codex-turn-metadata. Only an explicit normal turn bypasses interception;
* missing or unrecognized metadata retains the legacy opt-in prefix behavior.
*/
export function shouldInterceptShadowCall(
modelId: string,
configured: unknown,
headers: Headers,
): boolean {
return isShadowSourceModel(modelId, configured);
if (!isShadowSourceModel(modelId, configured)) return false;
const rawMetadata = headers.get("x-codex-turn-metadata");
if (rawMetadata === null) return true;

try {
const parsed = JSON.parse(rawMetadata) as { request_kind?: unknown };
return parsed?.request_kind !== "turn";

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 Distinguish Codex 0.147 helper turns before bypassing

When shadow interception is enabled with Codex 0.147, background helper requests can also carry request_kind: "turn" (the preceding implementation explicitly handled lidge-jun#1684 for this case), so this condition now returns false for both genuine foreground requests and helpers. Title, commit-message, and skill-orchestration calls therefore remain on Luna instead of using the configured low-effort replacement, regressing the opt-in feature for that client version. Use an additional foreground discriminator rather than treating request_kind alone as authoritative.

AGENTS.md reference: src/AGENTS.md:L10-L10

Useful? React with 👍 / 👎.

} catch {
return true;
}
}
1 change: 1 addition & 0 deletions src/server/responses/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1598,6 +1598,7 @@ async function handleResponsesInner(
if (_sci?.enabled && _sci.model && shouldInterceptShadowCall(
parsed.modelId,
_sci.sourceModels,
req.headers,
)) {
const _sciOriginal = parsed.modelId;
parsed.modelId = _sci.model;
Expand Down
32 changes: 22 additions & 10 deletions tests/responses-shadow-intercept.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,19 +58,32 @@ describe("isShadowSourceModel", () => {
});

describe("shouldInterceptShadowCall", () => {
test("intercepts every shadow source model unconditionally (#1684)", () => {
expect(shouldInterceptShadowCall("gpt-5.6-luna", undefined)).toBe(true);
expect(shouldInterceptShadowCall("gpt-5.6-luna-2026-08", undefined)).toBe(true);
test("does not intercept an explicit foreground turn", () => {
const headers = new Headers({
"x-codex-turn-metadata": JSON.stringify({ request_kind: "turn" }),
});
expect(shouldInterceptShadowCall("gpt-5.6-luna", undefined, headers)).toBe(false);
expect(shouldInterceptShadowCall("gpt-5.6-luna-2026-08", undefined, headers)).toBe(false);
});

test("intercepts maintenance, headerless, and malformed-metadata requests", () => {
const maintenance = new Headers({
"x-codex-turn-metadata": JSON.stringify({ request_kind: "memory" }),
});
const malformed = new Headers({ "x-codex-turn-metadata": "{not json" });
expect(shouldInterceptShadowCall("gpt-5.6-luna", undefined, maintenance)).toBe(true);
expect(shouldInterceptShadowCall("gpt-5.6-luna", undefined, new Headers())).toBe(true);
expect(shouldInterceptShadowCall("gpt-5.6-luna", undefined, malformed)).toBe(true);
});

test("does not intercept non-source models", () => {
expect(shouldInterceptShadowCall("gpt-5.6-terra", undefined)).toBe(false);
expect(shouldInterceptShadowCall("gpt-5.5", undefined)).toBe(false);
expect(shouldInterceptShadowCall("gpt-5.6-terra", undefined, new Headers())).toBe(false);
expect(shouldInterceptShadowCall("gpt-5.5", undefined, new Headers())).toBe(false);
});

test("respects configured sourceModels override", () => {
expect(shouldInterceptShadowCall("custom-helper-v2", ["custom-helper"])).toBe(true);
expect(shouldInterceptShadowCall("gpt-5.6-luna", ["custom-helper"])).toBe(false);
expect(shouldInterceptShadowCall("custom-helper-v2", ["custom-helper"], new Headers())).toBe(true);
expect(shouldInterceptShadowCall("gpt-5.6-luna", ["custom-helper"], new Headers())).toBe(false);
});
});

Expand Down Expand Up @@ -128,7 +141,7 @@ describe("shadow call intercept request path (issue #311)", () => {
expect(effort).toBe("low");
});

test("rewrites a gpt-5.6-luna turn request too (#1684)", async () => {
test("does not rewrite a gpt-5.6-luna foreground turn", async () => {
const bodies: Array<Record<string, unknown>> = [];
globalThis.fetch = (async (_url: unknown, init?: RequestInit) => {
bodies.push(JSON.parse(String(init?.body ?? "{}")) as Record<string, unknown>);
Expand All @@ -140,8 +153,7 @@ describe("shadow call intercept request path (issue #311)", () => {

await post(interceptConfig(), "gpt-5.6-luna", "turn");

expect(bodies.length).toBe(1);
expect(String(bodies[0]?.model ?? "")).toContain("grok-4.5");
expect(bodies.length).toBe(0);
});

test("leaves gpt-5.6-terra requests unrewritten", async () => {
Expand Down
Loading