{"record":{"id":"5a24b8d800c0d381","repo":"openclaw/openclaw","slug":"codex-settled-turn-finalization-returned-unexpecte","errorCode":null,"errorMessage":"Codex settled-turn finalization returned unexpected native item: ${unexpectedItem.type}","messagePattern":"Codex settled-turn finalization returned unexpected native item: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"extensions/codex/src/app-server/settled-turn-finalizer.ts","lineNumber":80,"sourceCode":"    }\n    if (item.type === \"userMessage\" && !promptEchoSeen) {\n      const content = Array.isArray(item.content) ? item.content : [];\n      const input = content[0];\n      const isPromptEcho =\n        content.length === 1 &&\n        isJsonObject(input) &&\n        input.type === \"text\" &&\n        input.text === attempt.prompt;\n      if (isPromptEcho) {\n        promptEchoSeen = true;\n        continue;\n      }\n    }\n    unexpectedItem = item;\n    break;\n  }\n  if (unexpectedItem) {\n    throw new Error(\n      `Codex settled-turn finalization returned unexpected native item: ${unexpectedItem.type}`,\n    );\n  }\n  const text = bounded.text.trim();\n  if (!text) {\n    return {\n      assistant: createAssistantMessage(attempt, \"\", {\n        tokenUsage: bounded.usage,\n        aborted: false,\n        promptError: null,\n      }),\n      ...(bounded.usage ? { usage: bounded.usage } : {}),\n    };\n  }\n  if (isSilentReplyText(text)) {\n    throw new Error(\"Codex settled-turn finalization completed without a visible answer\");\n  }\n","sourceCodeStart":62,"sourceCodeEnd":98,"githubUrl":"https://github.com/openclaw/openclaw/blob/01804a75319da4b69c9ab98ceaa30477e22b8c0b/extensions/codex/src/app-server/settled-turn-finalizer.ts#L62-L98","documentation":"Thrown when iterating the finalizer's bounded-turn output items and encountering an item whose type is not in FINALIZER_PASSIVE_ITEM_TYPES (agentMessage, reasoning) and not the prompt-echo userMessage. The finalizer is instructed to produce a single text answer with no external capabilities (requireNoExternalCapabilities: true), so any function_call, tool call, or stray userMessage is treated as a contract violation.","triggerScenarios":"The finalizer model emits a function_call or function_call_output despite requireNoExternalCapabilities; the model echoes something other than the prompt as a userMessage; a reasoning/agentMessage item is missing its type tag due to a protocol parsing drift.","commonSituations":"Model regression where the finalizer ignores developer instructions and tries to call a tool; app-server protocol change that renames an item type; prompt that the model parrots back in a non-echo userMessage slot.","solutions":["Inspect bounded.items to see the unexpected item type and payload; the message includes the type.","Confirm the bounded turn was launched with requireNoExternalCapabilities: true and isolation: \"private-stdio\".","If the protocol added a new passive item type, add it to FINALIZER_PASSIVE_ITEM_TYPES after confirming it carries no executable capability.","Tighten FINALIZER_DEVELOPER_INSTRUCTIONS or switch model if the finalizer persistently emits tool calls."],"exampleFix":"// before\nconst FINALIZER_PASSIVE_ITEM_TYPES = new Set([\"agentMessage\", \"reasoning\"]);\n// item.type === \"function_call\" -> throws\n\n// after (protocol added reasoning summary, verified non-executable)\nconst FINALIZER_PASSIVE_ITEM_TYPES = new Set([\"agentMessage\", \"reasoning\", \"reasoningSummary\"]);","handlingStrategy":"validation","validationCode":"const FINALIZER_ALLOWED_ITEM_TYPES = new Set([\"agentMessage\", \"reasoning\", \"userMessage\"]);\n\nfunction assertFinalizerItemsAcceptable(items: ReadonlyArray<{ type: string }>): void {\n  for (const item of items) {\n    if (!FINALIZER_ALLOWED_ITEM_TYPES.has(item.type)) {\n      throw new Error(`Finalizer produced disallowed item type up front: ${item.type}`);\n    }\n  }\n}\n\n// run after bounded.items returns, before runCodexSettledTurnFinalization proceeds\nassertFinalizerItemsAcceptable(bounded.items);","typeGuard":"function isPassiveFinalizerItem(item: { type: string }): boolean {\n  return item.type === \"agentMessage\" || item.type === \"reasoning\";\n}\n\nfunction isPromptEchoUserMessage(item: { type: string; content?: unknown }, prompt: string): boolean {\n  if (item.type !== \"userMessage\" || !Array.isArray(item.content) || item.content.length !== 1) return false;\n  const c = item.content[0] as { type?: string; text?: unknown };\n  return c?.type === \"text\" && c.text === prompt;\n}","tryCatchPattern":"try {\n  return await runCodexSettledTurnFinalization(operation, options);\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith(\"Codex settled-turn finalization returned unexpected native item\")) {\n    // The model ignored requireNoExternalCapabilities; re-run with stricter instructions or different model.\n    logger.error({ itemType: err.message }, \"finalizer produced unexpected item\");\n  }\n  throw err;\n}","preventionTips":["Always launch the finalizer with requireNoExternalCapabilities: true and isolation: \"private-stdio\".","If a new passive item type appears in the protocol, verify it carries no capability before adding it to FINALIZER_PASSIVE_ITEM_TYPES.","Monitor finalizer item types on model changes; a model that starts emitting function_call is a regression."],"tags":["codex","settled-turn","finalization","protocol","model-output"],"backgroundTag":null,"analyzedSha":"01804a75319da4b69c9ab98ceaa30477e22b8c0b","analyzedAt":"2026-08-12T04:37:58.197Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}