diff --git a/packages/subagent/subagent-codex/src/run.ts b/packages/subagent/subagent-codex/src/run.ts index fdf467c876..a75dd4f049 100644 --- a/packages/subagent/subagent-codex/src/run.ts +++ b/packages/subagent/subagent-codex/src/run.ts @@ -31,7 +31,7 @@ export type CodexPermissionMode = | 'approve-for-me' | 'dangerously-bypass-approvals-and-sandbox' -/** Codex CLI permission modes that cannot wait for a human response. */ +/** Native non-interactive Codex modes mapped to official `thread/start` fields. */ export const CODEX_PERMISSION_MODES = [ 'never', 'approve-for-me', @@ -158,6 +158,8 @@ export async function startCodexRun( const bytes = typeof chunk === 'string' ? Buffer.from(chunk) : chunk wire.observeStderr(bytes.toString()) try { + // Synchronous fd forwarding preserves byte order without owning a + // backpressure queue. A slow host sink can block this event-loop turn. writeFileSync(process.stderr.fd, bytes) } catch { // Host stderr is an observation sink, not a child-run failure authority. @@ -227,6 +229,8 @@ export async function startCodexRun( processFailure, ]) } catch (error: unknown) { + // Give stderr data already queued in Node one turn to reach the wire + // before settlement snapshots the diagnostic; later OS data is best-effort. await new Promise((resolve) => { setImmediate(resolve) }) throw error } diff --git a/packages/subagent/subagent-codex/src/wire.ts b/packages/subagent/subagent-codex/src/wire.ts index cfb24a7481..cd94695863 100644 --- a/packages/subagent/subagent-codex/src/wire.ts +++ b/packages/subagent/subagent-codex/src/wire.ts @@ -416,6 +416,11 @@ export class CodexAppServerWire { } } + /** + * Validate the request's thread and turn association. + * @returns `true` when the matching turn is still provisional, so the caller + * defers its diagnostic until `commitTurnId()`. + */ private validateRunIds( params: JsonObject, nullableTurn = false,