Environment
| Component |
Version |
| happy-coder |
0.13.0 |
| Claude Code |
2.0.76 |
| Node.js |
v22.19.0 |
| macOS |
26.2 (Build 25C56) |
| Terminal |
xterm-24bit |
| Shell |
zsh |
Summary
After switching from remote mode (mobile) back to local mode, terminal input becomes severely corrupted. Characters are dropped/eaten, the status line flashes erratically, and using slash commands (/) causes Claude Code CPU to spike to 100%, making the terminal completely unresponsive.
Steps to Reproduce
- Start happy-coder in local mode:
happy
- Send a message from the mobile app (triggers automatic switch to remote mode)
- Press double-space to switch back to local mode
- Note: Often requires pressing space 3-5+ times before the switch actually works
- Attempt to type in local mode
Expected Behavior
- Double-space switches cleanly to local mode
- Terminal displays chat history normally
- Typing works normally with all characters appearing
Actual Behavior
- Multiple spaces required: Double-space often doesn't work; need 3-5+ presses
- Missing chat history: Terminal doesn't refresh properly, missing conversation context
- Character loss: Typed characters are dropped/eaten intermittently
- Status line flashing: Duration and context window values flash back and forth
- Slash command crash: Pressing
/ causes Claude Code CPU to spike to 100%
- Complete unresponsiveness: Terminal becomes frozen, requiring
kill of both Claude Code and happy-coder processes
Additional Symptom
On exit, an EIO error is emitted:
EIO: i/o error, read
fd: 11,
syscall: "read",
errno: -5,
code: "EIO"
Root Cause Analysis
After investigating the source code, several issues were identified in the remote→local mode transition:
1. stdin state not properly reset after remote mode
In claudeRemoteLauncher finally block (~line 2980-2992 in index-B3gQr6vs.mjs):
} finally {
permissionHandler.reset();
process.stdin.off("data", abort);
if (process.stdin.isTTY) {
process.stdin.setRawMode(false);
}
if (inkInstance) {
inkInstance.unmount();
}
// ...
}
Problems:
stdin.pause() is never called - stdin remains in "flowing" mode
setEncoding("utf8") set during remote mode setup (~line 2693) is never reset
- Keypress listeners may not be fully removed
Compare to remote mode setup (~line 2689-2695):
if (hasTTY) {
process.stdin.resume();
if (process.stdin.isTTY) {
process.stdin.setRawMode(true);
}
process.stdin.setEncoding("utf8"); // This persists!
}
2. Race condition with Ink unmount
When inkInstance.unmount() is called, Ink's internal stdin handling may not fully clean up before claudeLocal spawns the new Claude Code process with stdio: ["inherit"].
3. Async cleanup not awaited
In the main cleanup handler (~line 5210-5215):
stopCaffeinate();
happyServer.stop(); // Not awaited
hookServer.stop(); // Not awaited
cleanupHookSettingsFile(hookSettingsPath);
logger.debug("[START] Cleanup complete, exiting");
process.exit(0); // Exits while async operations still running
This causes the EIO error on exit.
Attempted Fixes (Unsuccessful)
Added to the finally block:
process.stdin.removeAllListeners("keypress");
process.stdin.pause();
This did not resolve the issue, suggesting the problem may be deeper - possibly in how Claude Code handles inherited stdin when the terminal is in an inconsistent state, or a race condition with Ink.
Suggested Investigation Areas
- Ink stdin handling: Check if Ink fully releases stdin control on unmount
- Claude Code terminal setup: How does Claude Code initialize terminal when resuming a session with inherited stdio?
- Stream encoding: The
setEncoding("utf8") call may affect how Claude Code reads raw terminal input
- Timing: Add delays or proper awaits between Ink unmount and Claude spawn
Workaround
Currently none - the remote→local switch is essentially unusable. Users must avoid using mobile mode or restart happy-coder after each mobile interaction.
Log Reference
Relevant log entries showing the mode switch:
[01:02:37.816] [loop] Iteration with mode: remote
[01:02:58.064] [remote]: Switching to local mode via double space
[01:02:58.065] [remote]: doSwitch
[01:02:58.066] [remote]: launch finally
[01:02:58.076] [loop] Iteration with mode: local
[01:02:58.079] [local]: launch
[01:02:58.079] [ClaudeLocal] Will resume existing session: ...
The switch appears to complete in logs, but terminal state is corrupted.
Environment
Summary
After switching from remote mode (mobile) back to local mode, terminal input becomes severely corrupted. Characters are dropped/eaten, the status line flashes erratically, and using slash commands (
/) causes Claude Code CPU to spike to 100%, making the terminal completely unresponsive.Steps to Reproduce
happyExpected Behavior
Actual Behavior
/causes Claude Code CPU to spike to 100%killof both Claude Code and happy-coder processesAdditional Symptom
On exit, an EIO error is emitted:
Root Cause Analysis
After investigating the source code, several issues were identified in the remote→local mode transition:
1. stdin state not properly reset after remote mode
In
claudeRemoteLauncherfinally block (~line 2980-2992 inindex-B3gQr6vs.mjs):Problems:
stdin.pause()is never called - stdin remains in "flowing" modesetEncoding("utf8")set during remote mode setup (~line 2693) is never resetCompare to remote mode setup (~line 2689-2695):
2. Race condition with Ink unmount
When
inkInstance.unmount()is called, Ink's internal stdin handling may not fully clean up beforeclaudeLocalspawns the new Claude Code process withstdio: ["inherit"].3. Async cleanup not awaited
In the main cleanup handler (~line 5210-5215):
This causes the EIO error on exit.
Attempted Fixes (Unsuccessful)
Added to the finally block:
This did not resolve the issue, suggesting the problem may be deeper - possibly in how Claude Code handles inherited stdin when the terminal is in an inconsistent state, or a race condition with Ink.
Suggested Investigation Areas
setEncoding("utf8")call may affect how Claude Code reads raw terminal inputWorkaround
Currently none - the remote→local switch is essentially unusable. Users must avoid using mobile mode or restart happy-coder after each mobile interaction.
Log Reference
Relevant log entries showing the mode switch:
The switch appears to complete in logs, but terminal state is corrupted.