slopus/happy · error · Error

ACP session is not started

Error message

ACP session is not started

What it means

runAcp's message loop batches user prompts and only forwards them once the ACP session handshake produced an acpSessionId. If a prompt arrives and acpSessionId is still unset (session start never completed or failed), the loop throws this error before sending the turn.

Source

Thrown at packages/happy-cli/src/agent/acp/runAcp.ts:910

        logAcp('muted', `Outgoing models from ${opts.agentName}: not reported yet`);
      }
    }

    while (!shouldExit) {
      const waitSignal = abortController.signal;
      const batch = await messageQueue.waitForMessagesAndGetAsString(waitSignal);
      if (!batch) {
        if (shouldExit) {
          break;
        }
        if (waitSignal.aborted) {
          continue;
        }
        break;
      }

      if (!acpSessionId) {
        throw new Error('ACP session is not started');
      }

      logAcp('incoming', `Incoming prompt: ${formatUnknownForConsole(batch.message, ACP_EVENT_PREVIEW_CHARS)}`);
      sendEnvelopes(sessionManager.startTurn());
      const turnEnded = waitForTurnEnd();
      try {
        if (typeof batch.mode.permissionMode === 'string' && batch.mode.permissionMode.length > 0) {
          await switchPermissionModeIfRequested(batch.mode.permissionMode);
        }
        if (typeof batch.mode.model === 'string' && batch.mode.model.length > 0) {
          await switchModelIfRequested(batch.mode.model);
        }
        await backend.sendPrompt(acpSessionId, batch.message);
        await turnEnded;
        sendEnvelopes(sessionManager.endTurn('completed'));
        session.sendSessionEvent({ type: 'ready' });
        if (verbose) {
          logAcp('muted', `Outgoing prompt completion from ${opts.agentName}`);

View on GitHub (pinned to b824cd0a46)

Solutions

  1. Wait for the 'session started'/ready indicator before sending the first prompt.
  2. Check earlier logAcp/status output for a session-start failure and restart the backend.
  3. If the agent hangs at startup, verify the command runs and completes the ACP handshake standalone.
  4. Increase startup timeouts if the agent is slow but healthy.

Example fix

// before
await runAcp({ agentName: 'claude-code' }); // prompt sent instantly
// after
await ready;                 // wait for session-ready promise
await runAcp({ agentName: 'claude-code' });
Defensive patterns

Strategy: validation

Validate before calling

await sessionReadyPromise; // resolves when acpSessionId is set
// only then allow prompts:
submitPromptButton.enabled = true;

Type guard

function sessionStarted(state: { acpSessionId?: string | null }): state is { acpSessionId: string } {
  return typeof state.acpSessionId === 'string' && state.acpSessionId.length > 0;
}

Try / catch

try {
  await waitForTurnEnd();
} catch (err) {
  if (err instanceof Error && err.message === 'ACP session is not started') {
    console.error('Agent session never started; check agent startup logs and restart.');
  } else throw err;
}

Prevention

When it happens

Trigger: User submits a prompt while the agent's initialize/newSession handshake is still in flight; session start failed earlier (agent error/crash) but the UI still accepted input; first prompt raced ahead of session establishment.

Common situations: Typing immediately after launching `happy acp <agent>` on a slow-to-start agent; agent binary that hangs during handshake; retrying a prompt after a failed session without restarting.

Related errors


AI-assisted analysis of slopus/happy@b824cd0a46 (2026-08-31). Data as JSON: /api/errors/ff3d255d386df18b. Report an issue: GitHub.