-
Notifications
You must be signed in to change notification settings - Fork 180
fix(api): run in local mode when happy server is unreachable (prevents crashes) #92
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
ahundt
wants to merge
5
commits into
slopus:main
Choose a base branch
from
ahundt:fix/server-unreachable-graceful-fallback
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
fix(api): run in local mode when happy server is unreachable (prevents crashes) #92
ahundt
wants to merge
5
commits into
slopus:main
from
ahundt:fix/server-unreachable-graceful-fallback
+1,529
−97
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
When Happy API server is unreachable, the CLI was crashing with uncaught exceptions. Now handles connection errors gracefully and continues in offline mode. Changes: - api.ts: Add connection error handling (ECONNREFUSED, ENOTFOUND, ETIMEDOUT) - getOrCreateSession: Returns null when server unreachable - getOrCreateMachine: Returns minimal Machine object when server unreachable - Updated return types to reflect null handling - runClaude.ts, runCodex.ts: Handle null API responses with graceful exit - Show clear user message: "⚠️ Happy server unreachable - continuing in local mode" - Add comprehensive unit tests for server error scenarios This allows users to continue using Happy CLI in local mode even when the server is temporarily unavailable.
When Happy servers are unreachable, Claude/Codex now continue running locally instead of exiting. Background reconnection attempts use exponential backoff (5s-60s delay cap) with unlimited retries. Previous behavior: - Server unreachable at startup → process.exit(1) - User loses their work context What changed: - src/utils/offlineReconnection.ts: NEW shared utility with: - Exponential backoff using existing exponentialBackoffDelay() - Unlimited retries (delay caps at 60s, retries continue forever) - Auth failure detection (401 stops retrying) - Race condition handling (cancel during async ops) - Generic TSession type for backend transparency - src/utils/offlineReconnection.test.ts: NEW 24 comprehensive tests - src/claude/runClaude.ts: Offline fallback using claudeLocal() with hot reconnection via sessionScanner (syncs all JSONL messages) - src/codex/runCodex.ts: Offline fallback with session stub that swaps to real session on reconnection - src/api/api.ts: Return null on connection errors for graceful handling - src/api/api.test.ts: Tests for connection error handling User experience: - Startup offline: "⚠️ Happy server unreachable - running Claude locally" - On reconnect: "✅ Reconnected! Session syncing in background." - Auth failure: "❌ Authentication failed. Please re-authenticate."
Previous behavior: When server was unreachable, three separate warning messages would print from different call sites (api.getOrCreateSession, api.getOrCreateMachine, and runClaude/runCodex), resulting in confusing output like:⚠️ Happy server unreachable - working in offline mode⚠️ Happy server unreachable - working in offline mode⚠️ Happy server unreachable - running Claude locally What changed: - offlineReconnection.ts: Added OfflineState class with simple online/offline state machine that prints warning ONCE on first offline transition - offlineReconnection.ts: Added OfflineFailure type with operation, caller, errorCode, and url fields for detailed error context - offlineReconnection.ts: Added ERROR_DESCRIPTIONS map for human-readable error code translations (ECONNREFUSED, ETIMEDOUT, etc.) - api.ts: Changed console.log() to connectionState.fail() with full context - runClaude.ts, runCodex.ts: Added connectionState.setBackend() before API calls, removed redundant printOfflineWarning() calls - api.test.ts, offlineReconnection.test.ts: Updated assertions to use expect.stringContaining() and added connectionState.reset() in beforeEach New output format shows consolidated warning with actionable details:⚠️ Happy server unreachable - running Claude locally Failed: • Session creation: server not accepting connections (ECONNREFUSED) [api.getOrCreateSession] → Local work continues normally → Will reconnect automatically when server available
…nErrors
Previous behavior:
- offlineReconnection.ts handled all server errors including 403/409
- 403/409 showed "server unreachable" message (semantically wrong - server responded)
- Lost recovery action: no `happy doctor clean` guidance for re-auth conflicts
- Minimal machine object duplicated 3 times (DRY violation)
- ERROR_DESCRIPTIONS not exported (poor discoverability)
- path.test.ts mocked node:os which leaked to sessionScanner tests
What changed:
- src/utils/offlineReconnection.ts → src/utils/serverConnectionErrors.ts
- Renamed for accurate description (connection errors, not just offline)
- Export ERROR_DESCRIPTIONS for discoverability
- Added `details?: string[]` to OfflineFailure for multi-line context
- Updated module documentation
- src/api/api.ts
- Extract createMinimalMachine() helper (DRY - 4 call sites)
- 403/409 uses direct console.log (NOT connectionState) with recovery action:
"Run 'happy doctor clean' to reset local state"
- 5xx uses connectionState.fail() with details for auto-reconnect
- All HTTP error handling in catch block (axios throws on non-2xx)
- src/claude/utils/path.test.ts
- Remove vi.mock('node:os') that leaked to other tests
- Use CLAUDE_CONFIG_DIR env var (code already supports it)
- Cross-platform compatible, works with both npm and bun
- Updated imports in api.test.ts, runClaude.ts, runCodex.ts
Why:
- 403/409 are server rejections, not "server unreachable" - semantic accuracy
- Users need `happy doctor clean` recovery action for re-auth conflicts
- Exported ERROR_DESCRIPTIONS helps developers find error handling code
- File rename improves discoverability: serverConnectionErrors describes content
Testable:
- All 144 tests pass (0 fail)
- HAPPY_SERVER_URL=http://localhost:59999 happy --print "test"
Shows: "Machine registration failed: ECONNREFUSED - server not accepting connections"
Reverts: - README.md: restore claude-code-router link (line 39) - package.json: restore version 0.12.0 (was 0.12.0-0) - src/index.ts: restore --claude-env parsing (lines 271-284) Fixes: - src/api/api.test.ts:7-10: apply vi.hoisted pattern for vitest compatibility The vi.hoisted() wrapper ensures mock variables are available during vitest's module hoisting phase, fixing "Cannot access 'mockFn' before initialization" errors.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Problem
Happy crashes when the server is unreachable - Users see uncaught exceptions and can't use the CLI at all:
Related issues: #90 (Error 522), #71 (Error 500), #68 (no way to continue after MCP connect failure)
This is especially painful because Claude Code works fine locally - there's no reason happy should crash just because the remote sync server is down.
Solution
Graceful offline mode with background reconnection:
User Experience
Before:
After:
Changes
src/api/api.tssrc/utils/serverConnectionErrors.tssrc/claude/runClaude.tssrc/codex/runCodex.tsTechnical Details
Testing
Why Merge This