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

  1. Confirm the app window is fully open and on a codex/chatgpt page before starting the bridge
  2. Extend the timeoutMs passed to waitForCodexPageTarget
  3. Verify you attached to the correct DevTools port for the Codex App (not another Chromium/Node instance)
  4. 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

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


AI-assisted analysis of musistudio/claude-code-router@99f24806c6 (2026-08-27). Data as JSON: /api/errors/a9b1ebf233a49b00. Report an issue: GitHub.