{"record":{"id":"3236c07ed37f4cad","repo":"aaif-goose/goose","slug":"external-acp-backend-url-is-required","errorCode":null,"errorMessage":"External ACP backend URL is required","messagePattern":"External ACP backend URL is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ui/desktop/src/acp/url.ts","lineNumber":41,"sourceCode":"  }\n\n  const hostname = url.hostname.toLowerCase().replace(/^\\[(.*)\\]$/, '$1');\n  return hostname === 'localhost' || hostname === '::1' || isIpv4LoopbackLiteral(hostname);\n}\n\nfunction isIpv4LoopbackLiteral(hostname: string): boolean {\n  const octets = hostname.split('.');\n  if (octets.length !== 4 || octets.some((octet) => !/^\\d+$/.test(octet))) {\n    return false;\n  }\n\n  return octets.every((octet) => Number(octet) <= 255) && Number(octets[0]) === 127;\n}\n\nexport function normalizeAcpHttpBaseUrl(rawBaseUrl: string): string {\n  const trimmed = rawBaseUrl.trim();\n  if (!trimmed) {\n    throw new Error('External ACP backend URL is required');\n  }\n\n  const url = new URL(trimmed);\n  if (url.protocol !== 'http:' && url.protocol !== 'https:') {\n    throw new Error(`External ACP backend URL must use http: or https:, got ${url.protocol}`);\n  }\n\n  if (url.search || url.hash) {\n    throw new Error('External ACP backend URL must not include query parameters or fragments');\n  }\n\n  const pathname = url.pathname.replace(/\\/+$/, '');\n  if (pathname.endsWith('/acp')) {\n    throw new Error('External ACP backend URL must be the base URL before /acp');\n  }\n\n  return `${url.origin}${pathname}`;\n}","sourceCodeStart":23,"sourceCodeEnd":59,"githubUrl":"https://github.com/aaif-goose/goose/blob/3810898a7447ec3299be72e223d3570a7aabf0ab/ui/desktop/src/acp/url.ts#L23-L59","documentation":"Thrown by normalizeAcpHttpBaseUrl when the external ACP backend URL, after trimming, is an empty string. The function is the single entry point for turning a user-supplied base URL (external-backend mode) into status/acp endpoint URLs, and it validates stepwise: presence, scheme, query/fragment, path suffix. This first guard means no URL was provided at all.","triggerScenarios":"Calling normalizeAcpHttpBaseUrl('') or '   ' directly; external-backend mode enabled in the desktop app but the URL setting/env var never populated; a settings field read from an unset key (undefined coerced or defaulted to '').","commonSituations":"Enabling 'use external goose backend' without filling the URL input; env var (e.g. GOOSE_DESKTOP_EXTERNAL_ACP_URL) not exported in the shell that launched the app; fresh profile where the setting is unset.","solutions":["Provide the base URL of the running goose backend, e.g. http://127.0.0.1:8080 (no /acp suffix).","If using an env var, confirm it is set in the environment the Electron process actually inherits (not just your shell).","Validate the settings field before entering external mode so the user is prompted instead of hitting this throw."],"exampleFix":"// before\nconst trimmed = rawBaseUrl.trim();\nif (!trimmed) {\n  throw new Error('External ACP backend URL is required');\n}\n\n// after (caller-side gate with a friendly message)\nfunction requireExternalAcpUrl(url: string | undefined | null): string {\n  const value = (url ?? '').trim();\n  if (!value) throw new Error('External ACP backend URL is required');\n  return normalizeAcpHttpBaseUrl(value);\n}","handlingStrategy":"validation","validationCode":"// Gate external mode on a non-empty URL before any ACP call\nfunction externalAcpBaseUrlOrNull(settings: { url?: string | null }): string | null {\n  const value = (settings.url ?? '').trim();\n  return value.length > 0 ? value : null;\n}","typeGuard":"function isNonEmptyUrl(value: string | null | undefined): value is string {\n  return typeof value === 'string' && value.trim().length > 0;\n}","tryCatchPattern":"try {\n  const base = normalizeAcpHttpBaseUrl(inputUrl);\n} catch (error) {\n  if (/is required/.test(String(error))) {\n    setFieldError('backendUrl', 'Enter the goose backend base URL, e.g. http://127.0.0.1:8080');\n    return;\n  }\n  throw error;\n}","preventionTips":["Make the URL field required in the UI when external-backend mode is toggled on.","Persist a validated URL so subsequent launches never pass an empty string.","Trim input before saving settings."],"tags":["validation","url","configuration","acp"],"backgroundTag":null,"analyzedSha":"3810898a7447ec3299be72e223d3570a7aabf0ab","analyzedAt":"2026-08-16T10:14:26.282Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}