{"record":{"id":"3d4bad5d76be7afc","repo":"musistudio/claude-code-router","slug":"artifact-url-does-not-use-the-ccr-media-artifact-p","errorCode":null,"errorMessage":"Artifact URL does not use the CCR media artifact path.","messagePattern":"Artifact URL does not use the CCR media artifact path\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/core/src/agents/codex/media-preview-bridge.ts","lineNumber":278,"sourceCode":"      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)) {\n    throw new Error(\"Artifact URL contains an invalid identifier.\");\n  }\n  const keys = [...url.searchParams.keys()];\n  const token = url.searchParams.get(\"token\") || \"\";\n  if (keys.length !== 1 || keys[0] !== \"token\" || !/^[A-Za-z0-9_-]{32}$/.test(token)) {\n    throw new Error(\"Artifact URL contains an invalid access token.\");\n  }\n  return { artifactId, url };\n}\n\nasync function loadCodexMediaArtifact(validated: ValidatedArtifactUrl, signal: AbortSignal): Promise<LoadedMediaArtifact> {\n  let response: Response;\n  try {\n    response = await fetch(validated.url, {","sourceCodeStart":260,"sourceCodeEnd":296,"githubUrl":"https://github.com/musistudio/claude-code-router/blob/99f24806c6a2c660b16e53e95211c517448a6c90/packages/core/src/agents/codex/media-preview-bridge.ts#L260-L296","documentation":"validateCodexMediaArtifactUrl requires the URL pathname to start with the constant MEDIA_ARTIFACT_PATH_PREFIX (the CCR media artifact route). Any other path is rejected so the loader can only ever fetch known artifact endpoints from the gateway.","triggerScenarios":"Passing a URL on the correct gateway origin but with a different path, e.g. /api/media, /files/<id>, or a path missing the required prefix; also triggered by trailing-slash or case differences if they break the startsWith check.","commonSituations":"Artifact URLs from an older/newer gateway version that changed its route; hand-constructed URLs; reverse proxy that rewrites paths and strips or alters the prefix.","solutions":["Use artifact URLs exactly as issued by the gateway's media artifact endpoint","If the gateway route changed, update MEDIA_ARTIFACT_PATH_PREFIX (or upgrade this package) to match the deployed gateway version","Check reverse-proxy rewrite rules so the artifact path prefix survives proxying"],"exampleFix":"// before\nvalidateCodexMediaArtifactUrl(\"http://gw/files/uuid?token=...\", endpoint);\n// after\nvalidateCodexMediaArtifactUrl(`http://gw${MEDIA_ARTIFACT_PATH_PREFIX}uuid?token=...`, endpoint);","handlingStrategy":"validation","validationCode":"import { MEDIA_ARTIFACT_PATH_PREFIX } from '...';\nif (!new URL(artifactUrl).pathname.startsWith(MEDIA_ARTIFACT_PATH_PREFIX)) {\n  throw new Error('rejecting non-artifact path');\n}","typeGuard":"function isArtifactPath(value: string, prefix: string): boolean {\n  try { return new URL(value).pathname.startsWith(prefix); } catch { return false; }\n}","tryCatchPattern":null,"preventionTips":["Only use gateway-issued artifact URLs verbatim","Keep gateway and client versions in lockstep so route prefixes match","Check proxy rewrites don't alter the artifact path"],"tags":["url-validation","path-prefix","gateway"],"backgroundTag":"invalid-url-format","analyzedSha":"99f24806c6a2c660b16e53e95211c517448a6c90","analyzedAt":"2026-08-27T04:11:01.184Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}