aaif-goose/goose · critical

ACP URL is not available

Error message

ACP URL is not available

What it means

Thrown by openConnection() in the desktop UI when window.electron.getAcpUrl() resolves to a falsy value. That IPC call reaches main.ts (gooseServeLeases.getAcpUrl(windowId) ?? null), so the error means the Electron main process has no ACP WebSocket URL registered for this window: the goose serve backend either never started, exited, or the external-backend mode was not configured. The connection to the goose agent cannot even be attempted.

Source

Thrown at ui/desktop/src/acp/acpConnection.ts:132

  if (!pendingConnection) {
    const generation = connectionGeneration;
    let connectionAttempt: Promise<AcpConnection>;
    connectionAttempt = openConnection(generation).catch((error) => {
      if (pendingConnection === connectionAttempt) {
        pendingConnection = null;
      }
      throw error;
    });
    pendingConnection = connectionAttempt;
  }

  return pendingConnection;
}

async function openConnection(generation: number): Promise<AcpConnection> {
  const wsUrl = await window.electron.getAcpUrl();
  if (!wsUrl) {
    throw new Error('ACP URL is not available');
  }

  // Electron treats an explicitly passed undefined protocol as a subprotocol.
  const stream = createWebSocketStream(wsUrl, { protocols: [] });
  const client = connectGooseAcpClient(stream, createClientCallbacks());

  try {
    const initializeResponse = await withTimeout(
      client.connection.agent.request(methods.agent.initialize, {
        protocolVersion: PROTOCOL_VERSION,
        _meta: {
          'goose/useLoginShellPath': true,
        },
        clientCapabilities: {
          elicitation: { form: {} },
          _meta: {
            goose: {
              mcpHostCapabilities: DEFAULT_GOOSE_MCP_HOST_CAPABILITIES,

View on GitHub (pinned to 3810898a74)

Solutions

  1. Check main-process logs for the 'goose serve' spawn: if it exits immediately, fix the root cause (invalid config, missing binary, port conflict).
  2. If connecting to an external backend, set the external ACP base URL so getAcpUrl has a value instead of null.
  3. Retry the connection after a short delay — the lease is often registered milliseconds after the first attempt during startup.
  4. Verify the IPC handler 'get-acp-url' is registered before the renderer calls it and that the windowId matches an active lease.

Example fix

// before
const wsUrl = await window.electron.getAcpUrl();
if (!wsUrl) {
  throw new Error('ACP URL is not available');
}

// after (brief bounded retry for the startup race)
let wsUrl: string | null = null;
for (let attempt = 0; attempt < 10 && !wsUrl; attempt++) {
  wsUrl = await window.electron.getAcpUrl();
  if (!wsUrl) await new Promise((r) => setTimeout(r, 200));
}
if (!wsUrl) {
  throw new Error('ACP URL is not available (goose backend not started or exited)');
}
Defensive patterns

Strategy: retry

Validate before calling

// Wait until the main process has a backend URL for this window
async function waitForAcpUrl(timeoutMs = 5000): Promise<string> {
  const deadline = Date.now() + timeoutMs;
  for (;;) {
    const url = await window.electron.getAcpUrl();
    if (url) return url;
    if (Date.now() > deadline) throw new Error('ACP backend URL not available before timeout');
    await new Promise((r) => setTimeout(r, 200));
  }
}

Try / catch

try {
  const client = await getAcpClient();
} catch (error) {
  if (/ACP URL is not available/.test(String(error))) {
    // Backend not up (yet) — surface a 'starting backend' state and retry on next app event
    setBackendState('starting');
    scheduleReconnect();
    return;
  }
  throw error;
}

Prevention

When it happens

Trigger: Calling getAcpClient()/openConnection() before the main process has finished spawning 'goose serve' and recording the lease; after the serve process crashed or exited (gooseServeLeases.getAcpUrl then throws or returns null for that windowId); running with external-backend mode enabled but the URL setting missing.

Common situations: Startup race where the renderer connects faster than the backend binds its port; goose binary exits immediately (bad config, missing API key file) so the lease is torn down; window recreated with a new id that has no lease; development against an external goose daemon without setting the backend URL.

Related errors


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