musistudio/claude-code-router · error
Codex App CDP page target was not available${lastError ? `:
Error message
Codex App CDP page target was not available${lastError ? `: ${redactBridgeError(lastError)}` : "."} What it means
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.
Source
Thrown at packages/core/src/agents/codex/media-preview-bridge.ts:264
const deadline = Date.now() + timeoutMs;
let lastError: unknown;
while (!stopped() && Date.now() < deadline) {
try {
const response = await fetch(`http://127.0.0.1:${port}/json/list`, {
redirect: "error",
signal: AbortSignal.timeout(1_000)
});
if (!response.ok) throw new Error(`CDP target discovery returned HTTP ${response.status}.`);
const targets = await response.json() as DevToolsTarget[];
const pages = targets.filter((target) => target.type === "page" && target.webSocketDebuggerUrl);
const target = pages.find(isCodexAppPageTarget) || pages.find((entry) => (entry.url || "").startsWith("app://"));
if (target) return target;
} catch (error) {
lastError = error;
}
await sleep(codexMediaPreviewPollIntervalMs);
}
throw new Error(`Codex App CDP page target was not available${lastError ? `: ${redactBridgeError(lastError)}` : "."}`);
}
function isCodexAppPageTarget(target: DevToolsTarget): boolean {
if (target.type !== "page" || !target.webSocketDebuggerUrl) return false;
const url = target.url || "";
return url.startsWith("app://codex") || url.startsWith("app://chatgpt") || /\b(codex|chatgpt)\b/i.test(target.title || "");
}
function validateCodexMediaArtifactUrl(value: string, endpoint: string): ValidatedArtifactUrl {
const expected = new URL(endpoint);
const url = new URL(value);
if (url.protocol !== "http:" || url.origin !== expected.origin) throw new Error("Artifact origin is not the configured CCR gateway.");
if (url.username || url.password || url.hash) throw new Error("Artifact URL contains unsupported credentials or fragments.");
if (!url.pathname.startsWith(MEDIA_ARTIFACT_PATH_PREFIX)) throw new Error("Artifact URL does not use the CCR media artifact path.");
const encodedId = url.pathname.slice(MEDIA_ARTIFACT_PATH_PREFIX.length);
if (!encodedId || encodedId.includes("/")) throw new Error("Artifact URL contains an invalid identifier.");
const artifactId = decodeURIComponent(encodedId);
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)) {View on GitHub (pinned to 99f24806c6)
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
Example fix
// before const target = await waitForCodexPageTarget(port, 2000, stopped); // after const target = await waitForCodexPageTarget(port, 15000, stopped); // wait for app window to finish loading
Defensive patterns
Strategy: retry
Validate before calling
// probe the CDP target list before waiting
async function codexTargetLikelyPresent(port: number): Promise<boolean> {
const res = await fetch(`http://127.0.0.1:${port}/json`);
const targets = await res.json();
return targets.some((t: any) => t.type === 'page' && /^app:\/\/(codex|chatgpt)/.test(t.url || ''));
} Type guard
null
Try / catch
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; } Prevention
- 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
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- Codex App DevTools port was not available${lastError ? `: ${
- Timed out waiting for ChatGPT response: + requestId
- Grok CLI OAuth token refresh timed out after ${timeoutMs}ms.
- Grok CLI OIDC discovery timed out after ${timeoutMs}ms.
- Kimi CLI OAuth token refresh timed out after ${kimiOauthRefr
AI-assisted analysis of musistudio/claude-code-router@99f24806c6 (2026-08-27).
Data as JSON: /api/errors/a9b1ebf233a49b00.
Report an issue: GitHub.