{"record":{"id":"36e47054251f73fd","repo":"coder/code-server","slug":"failed-to-resolve-external-uri-uri-tostring","errorCode":null,"errorMessage":"Failed to resolve external URI: ${uri.toString()}. Could not determine base url because productConfiguration missing.","messagePattern":"Failed to resolve external URI: (.+?)\\. Could not determine base url because productConfiguration missing\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"patches/proxy-uri.diff","lineNumber":121,"sourceCode":"+import { extractLocalHostUriMetaDataForPortMapping, TunnelOptions, TunnelCreationOptions } from '../../../platform/tunnel/common/tunnel.js';\n import type { IURLCallbackProvider } from '../../../workbench/services/url/browser/urlService.js';\n import { create } from '../../../workbench/workbench.web.main.internal.js';\n \n@@ -612,6 +613,39 @@ class WorkspaceProvider implements IWork\n \t\tsettingsSyncOptions: config.settingsSyncOptions ? { enabled: config.settingsSyncOptions.enabled, } : undefined,\n \t\tworkspaceProvider: WorkspaceProvider.create(config),\n \t\turlCallbackProvider: new LocalStorageURLCallbackProvider(config.callbackRoute),\n+\t\tresolveExternalUri: (uri: URI): Promise<URI> => {\n+\t\t\tlet resolvedUri = uri\n+\t\t\tconst localhostMatch = extractLocalHostUriMetaDataForPortMapping(resolvedUri)\n+\t\t\tif (localhostMatch && resolvedUri.authority !== location.host) {\n+\t\t\t\tif (config.productConfiguration && config.productConfiguration.proxyEndpointTemplate) {\n+\t\t\t\t\tconst renderedTemplate = config.productConfiguration.proxyEndpointTemplate\n+\t\t\t\t\t\t.replace('{{port}}', localhostMatch.port.toString())\n+\t\t\t\t\t\t.replace('{{host}}', window.location.host)\n+\t\t\t\t\tresolvedUri = URI.parse(new URL(renderedTemplate, window.location.href).toString())\n+\t\t\t\t} else {\n+\t\t\t\t\tthrow new Error(`Failed to resolve external URI: ${uri.toString()}. Could not determine base url because productConfiguration missing.`)\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\t// If not localhost, return unmodified.\n+\t\t\treturn Promise.resolve(resolvedUri)\n+\t\t},\n+\t\ttunnelProvider: {\n+\t\t\ttunnelFactory: (tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions) => {\n+\t\t\t\tconst onDidDispose: Emitter<void> = new Emitter();\n+\t\t\t\tlet isDisposed = false;\n+\t\t\t\treturn Promise.resolve({\n+\t\t\t\t\tremoteAddress: tunnelOptions.remoteAddress,\n+\t\t\t\t\tlocalAddress: `localhost:${tunnelOptions.remoteAddress.port}`,\n+\t\t\t\t\tonDidDispose: onDidDispose.event,\n+\t\t\t\t\tdispose: () => {\n+\t\t\t\t\t\tif (!isDisposed) {\n+\t\t\t\t\t\t\tisDisposed = true;\n+\t\t\t\t\t\t\tonDidDispose.fire();\n+\t\t\t\t\t\t}","sourceCodeStart":103,"sourceCodeEnd":139,"githubUrl":"https://github.com/coder/code-server/blob/51f90a376b42e217b38937410fe2855e0c1db87e/patches/proxy-uri.diff#L103-L139","documentation":"Thrown by the resolveExternalUri implementation added in proxy-uri.diff when the URI to resolve is a localhost URI on a different authority than location.host AND config.productConfiguration or config.productConfiguration.proxyEndpointTemplate is missing. Without the template the code cannot construct the proxied URL for the external port, so it aborts rather than silently returning a broken link.","triggerScenarios":"An extension or workbench feature calls resolveExternalUri on a localhost:<port> URI whose authority differs from the current page host (a port-forwarding scenario), but the product configuration shipped to the browser lacks proxyEndpointTemplate. This is the typical 'open a forwarded port in browser' path.","commonSituations":"Running a fork/build that does not inject productConfiguration.proxyEndpointTemplate into the browser config; disabling the proxy feature while extensions still try to resolve localhost URIs; a version mismatch where the template key was renamed.","solutions":["Provide productConfiguration.proxyEndpointTemplate in the workbench config (e.g. a template like '/proxy/{{port}}/{{host}}').","Ensure code-server's build injects the product configuration including the proxy endpoint template into the browser.","If port proxying is disabled, prevent extensions from calling resolveExternalUri for localhost ports (disable forwarded-port open behavior).","Verify the template contains both {{port}} and {{host}} placeholders."],"exampleFix":"// In the workbench construction options sent to the browser:\nconfig.productConfiguration = {\n  ...config.productConfiguration,\n  proxyEndpointTemplate: '/proxy/{{port}}/{{host}}',\n}","handlingStrategy":"validation","validationCode":"// Ensure the proxy template is present before resolveExternalUri is reachable.\nif (!config.productConfiguration?.proxyEndpointTemplate) {\n  throw new Error('proxyEndpointTemplate must be configured for port proxying')\n}","typeGuard":"function hasProxyTemplate(c: any): boolean {\n  return !!c?.productConfiguration?.proxyEndpointTemplate\n    && c.productConfiguration.proxyEndpointTemplate.includes('{{port}}')\n}","tryCatchPattern":"try {\n  return await resolveExternalUri(uri)\n} catch (e) {\n  if (/productConfiguration missing/.test(e.message)) return uri // fall back to unmodified URI\n}","preventionTips":["Inject productConfiguration.proxyEndpointTemplate into the browser config.","Include both {{port}} and {{host}} placeholders in the template.","If proxying is disabled, also disable extension behaviors that call resolveExternalUri for localhost.","Keep the product configuration payload in sync with the workbench version."],"tags":["proxy","port-forwarding","resolveexternaluri","product-config","patch"],"backgroundTag":null,"analyzedSha":"51f90a376b42e217b38937410fe2855e0c1db87e","analyzedAt":"2026-08-12T11:27:34.273Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}