{"record":{"id":"b698c3bc63db218c","repo":"slopus/happy","slug":"server-unavailable","errorCode":null,"errorMessage":"Server unavailable","messagePattern":"Server unavailable","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/happy-cli/src/claude/runClaude.ts","lineNumber":190,"sourceCode":"            metadata,\n            metadataVersion: parseInt(reconnectMetadataVersion || '0', 10),\n            agentState: state,\n            agentStateVersion: parseInt(reconnectAgentStateVersion || '0', 10),\n        };\n    } else {\n        response = await api.getOrCreateSession({ tag: sessionTag, metadata, state });\n    }\n\n    // Handle server unreachable case - run Claude locally with hot reconnection\n    // Note: connectionState.notifyOffline() was already called by api.ts with error details\n    if (!response) {\n        let offlineSessionId: string | null = null;\n\n        const reconnection = startOfflineReconnection({\n            serverUrl: configuration.serverUrl,\n            onReconnected: async () => {\n                const resp = await api.getOrCreateSession({ tag: randomUUID(), metadata, state });\n                if (!resp) throw new Error('Server unavailable');\n                const session = api.sessionSyncClient(resp);\n                let latestClaudeGoalStatus: AgentGoalStatus | null = null;\n                const observedClaudeGoalRevisions = new Set<string>();\n                const goalCommandSupported = () => {\n                    const slashCommands = session.getMetadata()?.slashCommands ?? [];\n                    return slashCommands.includes('goal') || slashCommands.includes('/goal');\n                };\n                const currentClaudeSessionId = () => session.getMetadata()?.claudeSessionId ?? null;\n                const updateClaudeGoalState = (event: ClaudeGoalStatusTranscriptEvent) => {\n                    if (observedClaudeGoalRevisions.has(event.sourceRevision)) {\n                        return;\n                    }\n                    const capabilities = claudeGoalActionCapabilities({\n                        goalCommandSupported: goalCommandSupported(),\n                        observedGoalStatus: true,\n                        confirmedActions: CLAUDE_GOAL_ACTION_CONFIRMATIONS,\n                    });\n                    const goalStatus = mapClaudeGoalStatusEventToAgentGoalStatus(","sourceCodeStart":172,"sourceCodeEnd":208,"githubUrl":"https://github.com/slopus/happy/blob/b824cd0a4681d41af631a8e422a813873e4455b0/packages/happy-cli/src/claude/runClaude.ts#L172-L208","documentation":"When the network drops, startOfflineReconnection buffers and, once the server is back, calls api.getOrCreateSession to re-acquire the session. If that call resolves without a session payload, the reconnection callback throws 'Server unavailable' because reconnecting without a valid session record is impossible.","triggerScenarios":"During offline reconnection, getOrCreateSession({tag, metadata, state}) returns null/undefined — typically because the server is still partially up (accepting connections) but the session API is failing, or the request was answered by a fallback/proxy.","commonSituations":"Server restarting while the client reconnects mid-restart; reverse proxy returning an empty 2xx during maintenance; the tagged session could not be recreated server-side; flaky network causing half-open reconnections.","solutions":["Retry the reconnection once the server is fully healthy (check GET /health on configuration.serverUrl).","Check server logs around the reconnect timestamp for the failed getOrCreateSession call.","Verify no proxy/load balancer is swallowing the request with an empty success response.","Restart the happy session — offline state is persisted, so it can be re-synced.","Confirm server version compatibility with the CLI's API version."],"exampleFix":"// before\nconst resp = await api.getOrCreateSession({ tag: randomUUID(), metadata, state });\nif (!resp) throw new Error('Server unavailable');\n// after\nconst resp = await api.getOrCreateSession({ tag: randomUUID(), metadata, state });\nif (!resp) throw new RetriableError('Server unavailable: getOrCreateSession returned no session during reconnection');","handlingStrategy":"retry","validationCode":"const health = await fetch(`${configuration.serverUrl}/health`).then(r => r.ok).catch(() => false);\nif (!health) console.warn('Server still unhealthy — deferring reconnection');","typeGuard":null,"tryCatchPattern":"const reconnection = startOfflineReconnection({\n  serverUrl: configuration.serverUrl,\n  onReconnected: async () => {\n    for (let attempt = 1; attempt <= 3; attempt++) {\n      const resp = await api.getOrCreateSession({ tag: randomUUID(), metadata, state });\n      if (resp) { /* proceed */ return; }\n      await sleep(attempt * 2000);\n    }\n    logger.error('Reconnection failed: server unavailable after 3 attempts');\n  }\n});","preventionTips":["Monitor server health before assuming client bugs.","Avoid proxies that return empty 2xx responses during maintenance.","Keep CLI and server versions aligned.","Rely on offline persistence so a failed reconnection is recoverable by restart."],"tags":["network","reconnection","api"],"backgroundTag":"server-unavailable","analyzedSha":"b824cd0a4681d41af631a8e422a813873e4455b0","analyzedAt":"2026-08-31T23:12:36.205Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}