{"record":{"id":"6b29085fa8597c74","repo":"can1357/oh-my-pi","slug":"codex-returned-a-completion-without-running-web-se","errorCode":null,"errorMessage":"Codex returned a completion without running web search (no web_search_call event); refusing to treat a non-search answer as a search result","messagePattern":"Codex returned a completion without running web search \\(no web_search_call event\\); refusing to treat a non-search answer as a search result","errorType":"exception","errorClass":"CodexNoWebSearchError","httpStatus":502,"severity":"error","filePath":"packages/coding-agent/src/web/search/providers/codex.ts","lineNumber":655,"sourceCode":"\t\t\t}\n\t\t} else if (eventType === \"error\") {\n\t\t\tconst { code, message } = extractCodexSseError(rawEvent);\n\t\t\tthrow new SearchProviderError(\n\t\t\t\t\"codex\",\n\t\t\t\t`Codex error (${code}): ${message || \"Unknown error\"}`,\n\t\t\t\tclassifyCodexSseErrorStatus(code, message),\n\t\t\t);\n\t\t} else if (eventType === \"response.failed\") {\n\t\t\tconst { code, message } = extractCodexSseError(rawEvent);\n\t\t\tconst detail = code\n\t\t\t\t? `Codex request failed (${code}): ${message || \"Request failed\"}`\n\t\t\t\t: `Codex request failed: ${message || \"Request failed\"}`;\n\t\t\tthrow new SearchProviderError(\"codex\", detail, classifyCodexSseErrorStatus(code, message));\n\t\t}\n\t}\n\n\tif (!webSearchInvoked) {\n\t\tthrow new CodexNoWebSearchError();\n\t}\n\n\tconst finalAnswer = answerParts.join(\"\\n\\n\").trim();\n\tconst streamedAnswer = streamedAnswerParts.join(\"\").trim();\n\t// Throw to advance the chain whenever Codex emitted nothing but image\n\t// placeholder prose — including the case where the streamed delta itself\n\t// is the placeholder (the model occasionally streams the same text it\n\t// publishes as the final output_text).\n\tconst finalIsPlaceholder = finalAnswer.length > 0 && isImagePlaceholderAnswer(finalAnswer);\n\tconst streamedIsPlaceholder = streamedAnswer.length > 0 && isImagePlaceholderAnswer(streamedAnswer);\n\tconst hasFinalText = finalAnswer.length > 0 && !finalIsPlaceholder;\n\tconst hasStreamedText = streamedAnswer.length > 0 && !streamedIsPlaceholder;\n\tif (!hasFinalText && !hasStreamedText && sources.length === 0) {\n\t\tthrow new SearchProviderError(\"codex\", \"Codex returned image-only response\", 502);\n\t}\n\tconst answer = hasFinalText ? finalAnswer : hasStreamedText ? streamedAnswer : \"\";\n\n\t// Fallback: when Codex omits url_citation annotations, scrape markdown links","sourceCodeStart":637,"sourceCodeEnd":673,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/web/search/providers/codex.ts#L637-L673","documentation":"callCodexSearch tracks whether the stream contained any `web_search_call` (or `response.web_search_call`) event. If Codex completed without ever invoking its built-in web search tool, the answer cannot be trusted as a web search result, so a CodexNoWebSearchError is thrown to advance the provider fallback chain. This guards against treating a model's from-memory answer as a searched answer.","triggerScenarios":"Codex streams a normal completion (response.completed) but never emits a web_search_call output item — e.g. the model chose to answer directly, the web-search tool was stripped by a custom endpoint/proxy, or the backend silently disabled the tool for the account.","commonSituations":"Custom/third-party Codex-compatible endpoints that drop the `additional_tools` web-search item; models that ignore search directives on vague queries; account tiers where server-side web search is disabled.","solutions":["Verify the endpoint actually supports the built-in web_search tool (custom proxies often strip it) and switch to the official Codex endpoint.","Make the query explicitly search-oriented or pass search directives so the model invokes the tool.","Configure a different search provider (Brave, Tavily, Exa, Kagi) that performs server-side searches deterministically.","Check that the configured PI_CODEX_WEB_SEARCH_MODEL supports the web_search tool."],"exampleFix":"// before\nbaseURL: \"https://my-proxy.example.com/v1\" // proxy strips web_search tool\n// after\nbaseURL: \"https://chatgpt.com/backend-api/codex\" // official endpoint with web_search enabled","handlingStrategy":"fallback","validationCode":"// ensure the endpoint supports web_search before routing codex searches there:\nif (transport.customEndpoint && !endpointDeclaresWebSearchSupport(transport.baseUrl)) {\n  throw new Error(\"Custom endpoint does not advertise web_search support\");\n}","typeGuard":"function isCodexNoWebSearch(e: unknown): e is CodexNoWebSearchError {\n  return e instanceof CodexNoWebSearchError;\n}","tryCatchPattern":"try {\n  return await searchCodex(params);\n} catch (e) {\n  if (isCodexNoWebSearch(e)) return searchBrave(params); // deterministic server-side provider\n  throw e;\n}","preventionTips":["Prefer the official Codex endpoint; custom proxies commonly strip the web_search tool item.","Phrase queries so the model is clearly asked to search the web.","Verify the configured model supports the built-in web_search tool."],"tags":["codex","openai","web-search","tool-not-invoked","search-provider"],"backgroundTag":"web-search-tool-not-invoked","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}