fix: compact codex steer rejection disclosure

This commit is contained in:
Codex
2026-05-23 08:38:46 +00:00
parent 9c3d8809f1
commit 73f68021b4
5 changed files with 145 additions and 14 deletions
+54
View File
@@ -199,6 +199,24 @@ export function runCodeQueueCliSteerContract(): JsonRecord {
assertCondition(String(exhaustedDiagnostics.message || "").includes("The operation was aborted"), "diagnostics should include provider abort message", exhaustedDiagnostics);
assertCondition(nestedRecord(exhaustedDiagnostics, ["operatorGuidance"]).rawProxyEquivalentIsFallback === false, "raw proxy equivalent should be diagnostic, not fallback", exhaustedDiagnostics);
assertCondition(String(nestedRecord(exhausted, ["commands"]).rawProxy || "").includes("microservice proxy code-queue /api/tasks/direct_task/steer"), "failure should still expose raw proxy diagnostic command", exhausted);
assertCondition(nestedRecord(exhausted, ["steer"]).promptOmitted === true, "failed steer should omit prompt by default", exhausted);
assertCondition(!("request" in exhausted), "failed steer should omit request by default", exhausted);
assertCondition(!("upstreamBodyPreview" in exhaustedDiagnostics), "failed steer should omit upstream preview by default", exhaustedDiagnostics);
assertCondition(!String(JSON.stringify(exhausted)).includes("provider-gateway-http-fetch"), "failed steer default output should omit upstream body internals", exhausted);
assertCondition(String(nestedRecord(exhausted, ["commands"]).fullDetails || "").includes("--full"), "failed steer should suggest full disclosure command", exhausted);
assertCondition(String(nestedRecord(exhausted, ["commands"]).rawDetails || "").includes("--raw"), "failed steer should suggest raw disclosure command", exhausted);
const exhaustedFull = codexSteerTaskForTest("direct_task", ["final correction", "--retry-attempts", "1", "--retry-delay-ms", "0", "--full"], () => {
return { ok: false, status: 502, body: abortedTunnelBody };
}) as JsonRecord;
const exhaustedFullDiagnostics = nestedRecord(exhaustedFull, ["diagnostics"]);
assertCondition("request" in exhaustedFull, "--full failed steer should expose request metadata", exhaustedFull);
assertCondition("upstreamBodyPreview" in exhaustedFullDiagnostics, "--full failed steer should expose bounded upstream preview", exhaustedFullDiagnostics);
const exhaustedRaw = codexSteerTaskForTest("direct_task", ["final correction", "--retry-attempts", "1", "--retry-delay-ms", "0", "--raw"], () => {
return { ok: false, status: 502, body: abortedTunnelBody };
}) as JsonRecord;
assertCondition("rawFailure" in exhaustedRaw, "--raw failed steer should expose raw response", exhaustedRaw);
const terminalPrompt = `${"do not leak ".repeat(40)}tail-secret-marker`;
const terminalRejection = codexSteerTaskForTest("completed_task", [terminalPrompt], () => ({
@@ -232,6 +250,8 @@ export function runCodeQueueCliSteerContract(): JsonRecord {
assertCondition(String(terminalCommands.show || "").includes("codex task completed_task"), "terminal rejection should suggest show command", terminalCommands);
assertCondition(String(terminalCommands.read || "").includes("codex read completed_task"), "terminal rejection should suggest read command", terminalCommands);
assertCondition(String(terminalCommands.followUpSubmit || "").includes("codex submit --prompt-file <path> --reference-task-id completed_task"), "terminal rejection should suggest follow-up submit pattern", terminalCommands);
assertCondition(String(terminalCommands.fullDetails || "").includes("codex steer completed_task --prompt-file <path> --full"), "terminal rejection should suggest explicit full disclosure command", terminalCommands);
assertCondition(String(terminalCommands.rawDetails || "").includes("codex steer completed_task --prompt-file <path> --raw"), "terminal rejection should suggest explicit raw disclosure command", terminalCommands);
const terminalJson = JSON.stringify(terminalRejection);
assertCondition(!terminalJson.includes("tail-secret-marker"), "terminal rejection must not echo steer prompt", terminalRejection);
assertCondition(!terminalJson.includes("hidden task prompt"), "terminal rejection must not echo task prompt", terminalRejection);
@@ -239,6 +259,39 @@ export function runCodeQueueCliSteerContract(): JsonRecord {
assertCondition(!("request" in terminalRejection), "terminal rejection should omit request preview", terminalRejection);
assertCondition(!("diagnostics" in terminalRejection), "terminal rejection should omit bulky diagnostics", terminalRejection);
const terminalFull = codexSteerTaskForTest("completed_task", [terminalPrompt, "--full"], () => ({
ok: false,
status: 409,
body: {
ok: false,
error: "task does not have an active steerable turn",
task: {
id: "completed_task",
status: "succeeded",
terminalStatus: "completed",
prompt: `${"hidden task prompt ".repeat(60)}tail`,
output: [{ seq: 1, text: "noisy raw task output" }],
},
},
})) as JsonRecord;
const fullDiagnostics = nestedRecord(terminalFull, ["diagnostics"]);
assertCondition("upstreamBodyPreview" in fullDiagnostics, "--full should expose bounded upstream preview behind diagnostics", fullDiagnostics);
assertCondition(typeof fullDiagnostics.rawProxyEquivalent === "string", "--full should expose raw proxy equivalent", fullDiagnostics);
assertCondition(!("rawFailure" in terminalFull), "--full should not include raw upstream response", terminalFull);
const terminalRaw = codexSteerTaskForTest("completed_task", [terminalPrompt, "--raw"], () => ({
ok: false,
status: 409,
body: {
ok: false,
error: "task does not have an active steerable turn",
task: { id: "completed_task", status: "succeeded", terminalStatus: "completed" },
},
})) as JsonRecord;
const rawFailure = nestedRecord(terminalRaw, ["rawFailure"]);
const rawFailureTask = nestedRecord(rawFailure, ["body", "task"]);
assertCondition(rawFailure.status === 409 && rawFailureTask.status === "succeeded", "--raw should expose raw upstream failure only when requested", rawFailure);
return {
ok: true,
checks: [
@@ -256,6 +309,7 @@ export function runCodeQueueCliSteerContract(): JsonRecord {
"steer failure classification is JSON-consumable",
"retryable tunnel aborts are retried with bounded diagnostics",
"terminal steer rejection is compact and actionable",
"terminal steer rejection full/raw disclosure is explicit",
],
};
}
+84 -8
View File
@@ -222,6 +222,8 @@ interface CodexSteerOptions {
dryRun: boolean;
retryAttempts: number;
retryDelayMs: number;
full: boolean;
raw: boolean;
}
type CodexSteerFailureReason =
@@ -258,7 +260,6 @@ interface CodexSteerAttemptSummary {
scope: ClassifiedCodexSteerError["scope"] | null;
retryable: boolean;
message: string | null;
upstreamBodyPreview?: unknown;
}
interface CompactTaskMutationResponseOptions {
@@ -718,6 +719,22 @@ function classifySteerFailure(response: unknown, targetPath: string, stableProxy
};
}
function compactSteerFailureDiagnostics(diagnostics: ClassifiedCodexSteerError, includeDetails: boolean): Record<string, unknown> {
return {
reason: diagnostics.reason,
scope: diagnostics.scope,
status: diagnostics.status,
exitCode: diagnostics.exitCode,
retryable: diagnostics.retryable,
message: diagnostics.message,
recommendedCrossChecks: diagnostics.recommendedCrossChecks,
...(includeDetails ? {
upstreamBodyPreview: diagnostics.upstreamBodyPreview,
rawProxyEquivalent: diagnostics.rawProxyEquivalent,
} : {}),
};
}
function unwrapSteerResponse(response: unknown, targetPath: string, stableProxyPath: string, rawProxyEquivalent: string): { ok: true; upstream: { ok: unknown; status: unknown }; body: Record<string, unknown> } | { ok: false; diagnostics: ClassifiedCodexSteerError; rawResponse: unknown } {
const record = asRecord(response);
const body = responseBody(record);
@@ -771,6 +788,41 @@ function compactTerminalSteerRejection(taskId: string, response: unknown): Recor
};
}
function attachSteerDisclosure(
result: Record<string, unknown>,
disclosure: { full: boolean; raw: boolean },
response: unknown,
targetPath: string,
stableProxyPath: string,
rawProxyEquivalent: string,
): Record<string, unknown> {
if (!disclosure.full && !disclosure.raw) return result;
const record = asRecord(response);
const body = responseBody(record);
result.disclosure = {
defaultPolicy: "compact rejection; upstream body and raw response are only included with explicit --full or --raw",
full: disclosure.full,
raw: disclosure.raw,
defaultOmitted: ["request", "diagnostics.upstreamBodyPreview", "rawFailure"],
};
if (disclosure.full) {
result.request = {
path: targetPath,
stableProxyPath,
method: "POST",
bodySummary: {
promptOmitted: true,
},
};
result.diagnostics = {
upstreamBodyPreview: previewJson(body ?? record, { maxDepth: 4, maxArrayItems: 8, maxObjectKeys: 24, maxStringLength: 600 }),
rawProxyEquivalent,
};
}
if (disclosure.raw) result.rawFailure = response;
return result;
}
function steerSuccessAttempt(attempt: number, durationMs: number, upstream: { ok: unknown; status: unknown }): CodexSteerAttemptSummary {
const status = typeof upstream.status === "number" && Number.isFinite(upstream.status) ? upstream.status : null;
return {
@@ -797,7 +849,6 @@ function steerFailureAttempt(attempt: number, durationMs: number, diagnostics: C
scope: diagnostics.scope,
retryable: diagnostics.retryable,
message: diagnostics.message,
upstreamBodyPreview: diagnostics.upstreamBodyPreview,
};
}
@@ -4027,7 +4078,7 @@ function parseSubmitOptions(args: string[]): CodexSubmitOptions {
function parseSteerOptions(args: string[]): CodexSteerOptions {
assertKnownOptions(args, {
flags: ["--prompt-stdin", "--stdin", "--dry-run", "--no-retry"],
flags: ["--prompt-stdin", "--stdin", "--dry-run", "--no-retry", "--full", "--raw"],
valueOptions: ["--prompt-file", "--file", "--retry-attempts", "--retry-delay-ms"],
}, "codex steer");
const retryAttempts = hasFlag(args, "--no-retry")
@@ -4038,6 +4089,8 @@ function parseSteerOptions(args: string[]): CodexSteerOptions {
dryRun: hasFlag(args, "--dry-run"),
retryAttempts,
retryDelayMs: nonNegativeIntegerOption(args, ["--retry-delay-ms"], defaultSteerRetryDelayMs, maxSteerRetryDelayMs),
full: hasFlag(args, "--full") || hasFlag(args, "--raw"),
raw: hasFlag(args, "--raw"),
};
}
@@ -5997,7 +6050,7 @@ function codexSteerTask(taskId: string, args: string[], fetcher: CodexResponseFe
};
}
const attempts: CodexSteerAttemptSummary[] = [];
let failedResponse: { ok: false; diagnostics: ClassifiedCodexSteerError } | null = null;
let failedResponse: { ok: false; diagnostics: ClassifiedCodexSteerError; rawResponse: unknown } | null = null;
let successfulResponse: { ok: true; upstream: { ok: unknown; status: unknown }; body: Record<string, unknown> } | null = null;
for (let attempt = 1; attempt <= options.retryAttempts; attempt += 1) {
const startedAt = Date.now();
@@ -6011,18 +6064,27 @@ function codexSteerTask(taskId: string, args: string[], fetcher: CodexResponseFe
attempts.push(steerFailureAttempt(attempt, durationMs, response.diagnostics));
failedResponse = response;
const terminalRejection = compactTerminalSteerRejection(taskId, response.rawResponse);
if (terminalRejection !== null) return terminalRejection;
if (terminalRejection !== null) {
terminalRejection.commands = {
...(asRecord(terminalRejection.commands) ?? {}),
fullDetails: `bun scripts/cli.ts codex steer ${taskId} --prompt-file <path> --full`,
rawDetails: `bun scripts/cli.ts codex steer ${taskId} --prompt-file <path> --raw`,
};
return attachSteerDisclosure(terminalRejection, options, response.rawResponse, targetPath, stableProxyPath, rawProxyEquivalent);
}
if (!shouldRetrySteerFailure(response.diagnostics, attempt, options.retryAttempts)) break;
sleepSync(options.retryDelayMs);
}
if (successfulResponse === null) {
const diagnostics = failedResponse?.diagnostics ?? classifySteerFailure(null, targetPath, stableProxyPath, rawProxyEquivalent);
const transportDeliveryUnconfirmed = diagnostics.reason === "stable-proxy-failed" || diagnostics.reason === "backend-core-unreachable";
const compactDiagnostics = compactSteerFailureDiagnostics(diagnostics, options.full);
return {
ok: false,
steer: {
accepted: false,
prompt,
promptChars: options.prompt.length,
promptOmitted: true,
attempts,
deliveryUnconfirmed: transportDeliveryUnconfirmed,
retryPolicy: {
@@ -6034,9 +6096,14 @@ function codexSteerTask(taskId: string, args: string[], fetcher: CodexResponseFe
note: "codex steer retried only retryable stable-proxy/backend-core failures; raw microservice proxy uses the same tunnel and is diagnostic, not a lower-noise fallback.",
},
},
request,
...(options.full ? {
request: {
...request,
body: { prompt: { chars: options.prompt.length, textOmitted: true } },
},
} : {}),
diagnostics: {
...diagnostics,
...compactDiagnostics,
attempts,
retryPolicy: {
attempted: attempts.length,
@@ -6057,10 +6124,19 @@ function codexSteerTask(taskId: string, args: string[], fetcher: CodexResponseFe
dryRun: `bun scripts/cli.ts codex steer ${taskId} --prompt-file <path> --dry-run`,
retry: `bun scripts/cli.ts codex steer ${taskId} --prompt-file <path>`,
noRetry: `bun scripts/cli.ts codex steer ${taskId} --prompt-file <path> --no-retry`,
fullDetails: `bun scripts/cli.ts codex steer ${taskId} --prompt-file <path> --full`,
rawDetails: `bun scripts/cli.ts codex steer ${taskId} --prompt-file <path> --raw`,
rawProxy: rawProxyEquivalent,
tasks: "bun scripts/cli.ts codex tasks --view supervisor --limit 20",
health: "bun scripts/cli.ts microservice health code-queue",
},
disclosure: {
defaultPolicy: "compact rejection; request prompt, upstream body preview, and raw response require explicit --full or --raw",
full: options.full,
raw: options.raw,
defaultOmitted: ["request.body.prompt.text", "diagnostics.upstreamBodyPreview", "rawFailure"],
},
...(options.raw ? { rawFailure: failedResponse?.rawResponse ?? null } : {}),
};
}
return {
+3 -3
View File
@@ -63,7 +63,7 @@ export function rootHelp(): unknown {
{ command: "codex read <taskId>", description: "Mark one reviewed terminal task read and return terminal metadata plus final response; prompt/tool logs stay behind drill-down commands." },
{ command: "codex dev-ready", description: "Fetch execution-container readiness, including sanitized skill injection status from /api/dev-ready." },
{ command: "codex judge <taskId> --attempt N [--dry-run] [--include-prompt]", description: "Replay one stored Code Queue attempt through the same judge context builder and MiniMax judge call path used by the live queue worker." },
{ command: "codex steer <taskId> [prompt|--prompt-file path|--prompt-stdin] [--dry-run] [--no-retry|--retry-attempts N]", description: "Push a corrective prompt into a running Code Queue task; retryable tunnel aborts get bounded diagnostics, terminal-task rejection suggests codex task/read plus codex submit --reference-task-id <taskId>, and real success does not echo prompt text." },
{ command: "codex steer <taskId> [prompt|--prompt-file path|--prompt-stdin] [--dry-run] [--no-retry|--retry-attempts N] [--full|--raw]", description: "Push a corrective prompt into a running Code Queue task; retryable tunnel aborts get bounded diagnostics, terminal-task rejection suggests codex task/read plus codex submit --reference-task-id <taskId>, and upstream/raw rejection details require explicit --full or --raw." },
{ command: "codex interrupt|cancel <taskId>", description: "Request interrupt for a running Code Queue task, or cancel a queued/retry_wait task, through the same private proxy." },
{ command: "codex (queues [--full|--all] | queue create <queueId> | queue merge <sourceQueueId> --into <targetQueueId> | move <taskId> --queue <queueId>)", description: "List low-noise queue summaries by default, including effective activity counts that distinguish scheduler-local queues, DB running tasks, and heartbeat-fresh runners; full queue rows require --full/--all." },
{ command: "job list [--limit N] [--include-command]", description: "List async jobs from .state/jobs with a bounded default page." },
@@ -265,7 +265,7 @@ function codexHelp(): unknown {
"bun scripts/cli.ts codex skills-sync --dry-run [--full]",
"bun scripts/cli.ts codex pr-preflight [--remote] [--push-dry-run --push-dry-run-ref refs/heads/probe/<name>] [--pr-create-dry-run --pr-create-dry-run-head <head>] [--issue N] [--full|--raw]",
"bun scripts/cli.ts codex judge <taskId> --attempt N [--dry-run] [--include-prompt]",
"bun scripts/cli.ts codex steer <taskId> [prompt|--prompt-file path|--prompt-stdin] [--dry-run] [--no-retry|--retry-attempts N]",
"bun scripts/cli.ts codex steer <taskId> [prompt|--prompt-file path|--prompt-stdin] [--dry-run] [--no-retry|--retry-attempts N] [--full|--raw]",
"bun scripts/cli.ts codex interrupt|cancel <taskId>",
"bun scripts/cli.ts codex queues [--full|--all] [--limit N] [--page N|--offset N] | queue create <queueId> | queue merge <sourceQueueId> --into <targetQueueId> | move <taskId> --queue <queueId>",
],
@@ -323,7 +323,7 @@ function codexHelp(): unknown {
embeddedIn: ["codex submit --dry-run", "codex steer --dry-run"],
reference: "docs/reference/code-queue-supervision.md#dev-测试授权分级",
},
description: "Operate Code Queue through the stable backend-core private proxy path with bounded activity summaries for queue and supervisor views. Real submit/steer success is a low-noise write confirmation and does not echo prompt text; terminal steer rejection returns compact status plus codex task/read/submit follow-up commands.",
description: "Operate Code Queue through the stable backend-core private proxy path with bounded activity summaries for queue and supervisor views. Real submit/steer success is a low-noise write confirmation and does not echo prompt text; terminal steer rejection returns compact status plus codex task/read/submit follow-up commands, with upstream/raw details behind --full or --raw.",
};
}