slopus/happy · error

Server unavailable

Error message

Server unavailable

What it means

setupOfflineReconnection starts a background reconnection loop; when connectivity returns, onReconnected calls api.getOrCreateSession to re-acquire the session. If the API responds but returns a falsy response (session could not be created/retrieved), it throws 'Server unavailable' to signal the swap to a live session failed. It represents a server-side refusal/empty response rather than a client network error.

Source

Thrown at packages/happy-cli/src/utils/setupOfflineReconnection.ts:93

 * ```
 */
export function setupOfflineReconnection(opts: SetupOfflineReconnectionOptions): SetupOfflineReconnectionResult {
    const { api, sessionTag, metadata, state, response, onSessionSwap } = opts;

    let session: ApiSessionClient;
    let reconnectionHandle: ReturnType<typeof startOfflineReconnection<ApiSessionClient>> | null = null;

    // Note: connectionState.notifyOffline() was already called by api.ts with error details
    if (!response) {
        // Create a no-op session stub for offline mode using shared utility
        session = createOfflineSessionStub(sessionTag);

        // Start background reconnection
        reconnectionHandle = startOfflineReconnection<ApiSessionClient>({
            serverUrl: configuration.serverUrl,
            onReconnected: async () => {
                const resp = await api.getOrCreateSession({ tag: sessionTag, metadata, state });
                if (!resp) throw new Error('Server unavailable');
                const realSession = api.sessionSyncClient(resp);
                // Notify caller to swap the session reference
                onSessionSwap(realSession);
                return realSession;
            },
            onNotify: (msg) => {
                // Log to console - this matches Claude's behavior
                console.log(msg);
            }
        });

        return { session, reconnectionHandle, isOffline: true };
    } else {
        session = api.sessionSyncClient(response);
        return { session, reconnectionHandle: null, isOffline: false };
    }
}

View on GitHub (pinned to b824cd0a46)

Solutions

  1. Retry — reconnection may succeed once the server stabilizes; the reconnection loop usually re-attempts.
  2. Inspect server logs / API health for why getOrCreateSession returned no session for that tag.
  3. Verify client and server versions agree on the getOrCreateSession response shape.
  4. Restart the CLI session; if the server truly lost the session, create a fresh one.

Example fix

// before
const resp = await api.getOrCreateSession({ tag: sessionTag, metadata, state });
if (!resp) throw new Error('Server unavailable');
// after
const resp = await api.getOrCreateSession({ tag: sessionTag, metadata, state });
if (!resp) throw new Error(`Server did not return a session for tag "${sessionTag}"; check server health and retry`);
Defensive patterns

Strategy: retry

Try / catch

reconnectionHandle = startOfflineReconnection<ApiSessionClient>({
  ...opts,
  onReconnected: async () => {
    for (let attempt = 1; attempt <= 3; attempt++) {
      const resp = await api.getOrCreateSession({ tag: sessionTag, metadata, state });
      if (resp) return api.sessionSyncClient(resp);
      await new Promise(r => setTimeout(r, 500 * attempt));
    }
    throw new Error('Server unavailable after retries');
  },
});

Prevention

When it happens

Trigger: After offline reconnection succeeds at the transport level, api.getOrCreateSession({ tag, metadata, state }) resolves to null/undefined — e.g. server returned an empty body, an error envelope parsed to nothing, or the session could not be re-created.

Common situations: Server under load returning empty/failed responses after an outage; session tag/state rejected silently; API version mismatch so the response shape isn't what the client expects; load balancer returning 200 with empty body.

Related errors


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