{"record":{"id":"d9dbd8549a2d004b","repo":"different-ai/openwork","slug":"source-fetch-failed","errorCode":"source_fetch_failed","errorMessage":"${error instanceof Error ? error.message : \"The app URL could not be downloaded.\"}","messagePattern":"\\$\\{error instanceof Error \\? error\\.message : \"The app URL could not be downloaded\\.\"\\}","errorType":"http","errorClass":"RemoteMcpAppError","httpStatus":502,"severity":"error","filePath":"ee/apps/den-api/src/remote-mcp-apps.ts","lineNumber":292,"sourceCode":"    return new TextDecoder(\"utf-8\", { fatal: true, ignoreBOM: true }).decode(bytes)\n  } catch {\n    throw new RemoteMcpAppError(422, \"invalid_encoding\", \"Remote MCP Apps must be UTF-8 HTML.\")\n  }\n}\n\nexport async function fetchRemoteMcpApp(sourceUrl: string) {\n  const { env } = await import(\"./env.js\")\n  const normalizedUrl = validateRemoteMcpAppSourceUrl(sourceUrl, env.allowPrivateMcpUrls)\n  const guardedFetch = env.allowPrivateMcpUrls ? createRealmSafeFetch() : createGuardedFetch()\n  let response: Response\n  try {\n    response = await guardedFetch(normalizedUrl, {\n      headers: { accept: \"text/html,application/xhtml+xml;q=0.9,text/plain;q=0.5\" },\n      signal: AbortSignal.timeout(REMOTE_MCP_APP_FETCH_TIMEOUT_MS),\n    })\n  } catch (error) {\n    if (error instanceof RemoteMcpAppError) throw error\n    throw new RemoteMcpAppError(502, \"source_fetch_failed\", error instanceof Error ? error.message : \"The app URL could not be downloaded.\")\n  }\n  if (!response.ok) {\n    await response.body?.cancel()\n    throw new RemoteMcpAppError(502, \"source_fetch_failed\", `The app URL returned HTTP ${response.status}.`)\n  }\n  const contentType = response.headers.get(\"content-type\")\n  try {\n    validateRemoteMcpAppContentType(contentType)\n  } catch (error) {\n    await response.body?.cancel()\n    throw error\n  }\n  const html = await boundedResponseText(response)\n  const inspected = inspectRemoteMcpAppHtml(html)\n  const resolvedSourceUrl = validateRemoteMcpAppSourceUrl(response.url || normalizedUrl, env.allowPrivateMcpUrls)\n  return {\n    ...inspected,\n    html,","sourceCodeStart":274,"sourceCodeEnd":310,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/ee/apps/den-api/src/remote-mcp-apps.ts#L274-L310","documentation":"fetchRemoteMcpApp wraps the outbound guardedFetch download in a try/catch. Any failure that is not already a RemoteMcpAppError (network failure, DNS error, timeout from AbortSignal.timeout, TLS error, invalid URL behavior) is rethrown as a 502 source_fetch_failed with the underlying error message, or a generic fallback message.","triggerScenarios":"guardedFetch throws while fetching the validated sourceUrl: connection refused/reset, DNS resolution failure, TLS certificate errors, or the fetch exceeding REMOTE_MCP_APP_FETCH_TIMEOUT_MS via AbortSignal.timeout.","commonSituations":"Typo in the source URL hostname; source server temporarily down; private-network URL blocked (SSRF guard) or allowPrivateMcpUrls disabled; slow origin exceeding the fetch timeout; self-signed certificates.","solutions":["Check the source URL is reachable: curl it from the den-api host and fix the hostname/DNS.","Re-register the app with the correct, publicly reachable URL.","If the origin is legitimately slow, reduce the page size or check for network issues; the timeout is REMOTE_MCP_APP_FETCH_TIMEOUT_MS.","For internal/private URLs, enable allowPrivateMcpUrls in the environment configuration.","Fix TLS (valid certificate chain) on the source server."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"const probe = await fetch(sourceUrl, { method: \"HEAD\" }).catch((e) => { throw new Error(`Unreachable before import: ${e.message}`); });\nif (!probe.ok) throw new Error(`Probe failed with HTTP ${probe.status}`);","typeGuard":null,"tryCatchPattern":"try {\n  await importRemoteMcpApp({ sourceUrl, ... });\n} catch (e) {\n  if (e instanceof RemoteMcpAppError && e.code === \"source_fetch_failed\") {\n    // inspect e.message for the underlying cause; retry with backoff for transient network errors\n  } else throw e;\n}","preventionTips":["curl the source URL from the den-api host before registering it.","Use stable, public, permanently-hosted URLs (avoid expiring signed links).","Ensure DNS and TLS are valid for the origin domain.","Keep the app host fast; the fetch has AbortSignal.timeout(REMOTE_MCP_APP_FETCH_TIMEOUT_MS)."],"tags":["network","fetch","timeout","remote-mcp-app"],"backgroundTag":"fetch-failed","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}