aaif-goose/goose · error

External ACP backend URL is required

Error message

External ACP backend URL is required

What it means

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.

Source

Thrown at ui/desktop/src/acp/url.ts:41

  }

  const hostname = url.hostname.toLowerCase().replace(/^\[(.*)\]$/, '$1');
  return hostname === 'localhost' || hostname === '::1' || isIpv4LoopbackLiteral(hostname);
}

function isIpv4LoopbackLiteral(hostname: string): boolean {
  const octets = hostname.split('.');
  if (octets.length !== 4 || octets.some((octet) => !/^\d+$/.test(octet))) {
    return false;
  }

  return octets.every((octet) => Number(octet) <= 255) && Number(octets[0]) === 127;
}

export function normalizeAcpHttpBaseUrl(rawBaseUrl: string): string {
  const trimmed = rawBaseUrl.trim();
  if (!trimmed) {
    throw new Error('External ACP backend URL is required');
  }

  const url = new URL(trimmed);
  if (url.protocol !== 'http:' && url.protocol !== 'https:') {
    throw new Error(`External ACP backend URL must use http: or https:, got ${url.protocol}`);
  }

  if (url.search || url.hash) {
    throw new Error('External ACP backend URL must not include query parameters or fragments');
  }

  const pathname = url.pathname.replace(/\/+$/, '');
  if (pathname.endsWith('/acp')) {
    throw new Error('External ACP backend URL must be the base URL before /acp');
  }

  return `${url.origin}${pathname}`;
}

View on GitHub (pinned to 3810898a74)

Solutions

  1. Provide the base URL of the running goose backend, e.g. http://127.0.0.1:8080 (no /acp suffix).
  2. If using an env var, confirm it is set in the environment the Electron process actually inherits (not just your shell).
  3. Validate the settings field before entering external mode so the user is prompted instead of hitting this throw.

Example fix

// before
const trimmed = rawBaseUrl.trim();
if (!trimmed) {
  throw new Error('External ACP backend URL is required');
}

// after (caller-side gate with a friendly message)
function requireExternalAcpUrl(url: string | undefined | null): string {
  const value = (url ?? '').trim();
  if (!value) throw new Error('External ACP backend URL is required');
  return normalizeAcpHttpBaseUrl(value);
}
Defensive patterns

Strategy: validation

Validate before calling

// Gate external mode on a non-empty URL before any ACP call
function externalAcpBaseUrlOrNull(settings: { url?: string | null }): string | null {
  const value = (settings.url ?? '').trim();
  return value.length > 0 ? value : null;
}

Type guard

function isNonEmptyUrl(value: string | null | undefined): value is string {
  return typeof value === 'string' && value.trim().length > 0;
}

Try / catch

try {
  const base = normalizeAcpHttpBaseUrl(inputUrl);
} catch (error) {
  if (/is required/.test(String(error))) {
    setFieldError('backendUrl', 'Enter the goose backend base URL, e.g. http://127.0.0.1:8080');
    return;
  }
  throw error;
}

Prevention

When it happens

Trigger: 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 '').

Common situations: 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.

Related errors


AI-assisted analysis of aaif-goose/goose@3810898a74 (2026-08-16). Data as JSON: /api/errors/3236c07ed37f4cad. Report an issue: GitHub.