Skip to content

CLI exits instead of switching to remote mode (ExitCodeError overrides switch intent) #941

@ljk53

Description

@ljk53

Bug

When switching from local to remote mode (triggered from mobile), the CLI exits instead of switching. The doSwitch callback correctly sets exitReason = { type: "switch" }, but the subsequent ExitCodeError catch block unconditionally overwrites it with { type: "exit", code: 143 }.

Steps to reproduce

  1. Start happy CLI in local mode
  2. From mobile app, trigger switch to remote mode (press space)
  3. Expected: CLI switches to remote mode
  4. Actual: CLI exits completely

Root cause

In the main session loop, when a switch is triggered:

  1. doSwitch sets exitReason = { type: "switch" }
  2. Claude process receives SIGTERM → exits with code 143
  3. Catch block handles ExitCodeError(143)
  4. Bug: catch block unconditionally sets exitReason = { type: "exit", code: 143 }, destroying the switch intent
  5. Loop sees type === "exit" and terminates

Fix

Guard the ExitCodeError catch to preserve an existing switch intent:

  if (e instanceof ExitCodeError) {
    if (exitReason && exitReason.type === "switch") {
      break; // preserve switch intent — exit code 143 is expected from SIGTERM
    }
    session.client.closeClaudeSessionTurn("failed");
    exitReason = { type: "exit", code: e.exitCode };
    break;
  }

Confirmed with logging

Added debug logging to the catch block. On reproduction, logs show:

[local]: exitReason at catch time: {"type":"switch"}
[local]: ExitCodeError (code 143) but exitReason is already 'switch', preserving switch intent

With the guard in place, the switch completes successfully.

Environment

  • happy 1.1.3 (npm global)
  • Node.js v20.19.5
  • Ubuntu 22.04, x86_64

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions