{"record":{"id":"a9b1ebf233a49b00","repo":"musistudio/claude-code-router","slug":"codex-app-cdp-page-target-was-not-available-laste","errorCode":null,"errorMessage":"Codex App CDP page target was not available${lastError ? `: ${redactBridgeError(lastError)}` : \".\"}","messagePattern":"Codex App CDP page target was not available(.+?)` : \"\\.\"\\}","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/core/src/agents/codex/media-preview-bridge.ts","lineNumber":264,"sourceCode":"  const deadline = Date.now() + timeoutMs;\n  let lastError: unknown;\n  while (!stopped() && Date.now() < deadline) {\n    try {\n      const response = await fetch(`http://127.0.0.1:${port}/json/list`, {\n        redirect: \"error\",\n        signal: AbortSignal.timeout(1_000)\n      });\n      if (!response.ok) throw new Error(`CDP target discovery returned HTTP ${response.status}.`);\n      const targets = await response.json() as DevToolsTarget[];\n      const pages = targets.filter((target) => target.type === \"page\" && target.webSocketDebuggerUrl);\n      const target = pages.find(isCodexAppPageTarget) || pages.find((entry) => (entry.url || \"\").startsWith(\"app://\"));\n      if (target) return target;\n    } catch (error) {\n      lastError = error;\n    }\n    await sleep(codexMediaPreviewPollIntervalMs);\n  }\n  throw new Error(`Codex App CDP page target was not available${lastError ? `: ${redactBridgeError(lastError)}` : \".\"}`);\n}\n\nfunction isCodexAppPageTarget(target: DevToolsTarget): boolean {\n  if (target.type !== \"page\" || !target.webSocketDebuggerUrl) return false;\n  const url = target.url || \"\";\n  return url.startsWith(\"app://codex\") || url.startsWith(\"app://chatgpt\") || /\\b(codex|chatgpt)\\b/i.test(target.title || \"\");\n}\n\nfunction validateCodexMediaArtifactUrl(value: string, endpoint: string): ValidatedArtifactUrl {\n  const expected = new URL(endpoint);\n  const url = new URL(value);\n  if (url.protocol !== \"http:\" || url.origin !== expected.origin) throw new Error(\"Artifact origin is not the configured CCR gateway.\");\n  if (url.username || url.password || url.hash) throw new Error(\"Artifact URL contains unsupported credentials or fragments.\");\n  if (!url.pathname.startsWith(MEDIA_ARTIFACT_PATH_PREFIX)) throw new Error(\"Artifact URL does not use the CCR media artifact path.\");\n  const encodedId = url.pathname.slice(MEDIA_ARTIFACT_PATH_PREFIX.length);\n  if (!encodedId || encodedId.includes(\"/\")) throw new Error(\"Artifact URL contains an invalid identifier.\");\n  const artifactId = decodeURIComponent(encodedId);\n  if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(artifactId)) {","sourceCodeStart":246,"sourceCodeEnd":282,"githubUrl":"https://github.com/musistudio/claude-code-router/blob/99f24806c6a2c660b16e53e95211c517448a6c90/packages/core/src/agents/codex/media-preview-bridge.ts#L246-L282","documentation":"waitForCodexPageTarget polls the CDP /json target list (via the port from waitForCodexDevToolsPort) for a page target matching isCodexAppPageTarget and throws this when the deadline expires without finding one. lastError (redacted) captures the most recent HTTP/CDP failure, if any.","triggerScenarios":"CDP is reachable but no target with type 'page', a webSocketDebuggerUrl, and an app://codex|app://chatgpt URL or codex/chatgpt title appears before the timeout; or every poll of the target list threw (connection reset, wrong port).","commonSituations":"Codex App still loading its window when the poll window closed; app version changed its page URL scheme away from app://codex; attached to the wrong debugging port (another Chromium instance); window closed mid-poll.","solutions":["Confirm the app window is fully open and on a codex/chatgpt page before starting the bridge","Extend the timeoutMs passed to waitForCodexPageTarget","Verify you attached to the correct DevTools port for the Codex App (not another Chromium/Node instance)","If the app's URL scheme changed in a new version, update the matching in isCodexAppPageTarget or pin a compatible app version"],"exampleFix":"// before\nconst target = await waitForCodexPageTarget(port, 2000, stopped);\n// after\nconst target = await waitForCodexPageTarget(port, 15000, stopped); // wait for app window to finish loading","handlingStrategy":"retry","validationCode":"// probe the CDP target list before waiting\nasync function codexTargetLikelyPresent(port: number): Promise<boolean> {\n  const res = await fetch(`http://127.0.0.1:${port}/json`);\n  const targets = await res.json();\n  return targets.some((t: any) => t.type === 'page' && /^app:\\/\\/(codex|chatgpt)/.test(t.url || ''));\n}","typeGuard":"null","tryCatchPattern":"try { const target = await waitForCodexPageTarget(port, timeoutMs, stopped); } catch (e) { if (e instanceof Error && e.message.includes('page target was not available')) { /* wait for app window, then retry */ } else throw e; }","preventionTips":["Start the bridge only after the app window finishes loading","Verify the port belongs to the Codex App, not another Chromium","Retry with backoff instead of a single long poll"],"tags":["cdp","page-target","codex-app","timeout"],"backgroundTag":"cdp-target-not-found","analyzedSha":"99f24806c6a2c660b16e53e95211c517448a6c90","analyzedAt":"2026-08-27T04:11:01.184Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}