Skip to content
This repository was archived by the owner on Feb 14, 2026. It is now read-only.

Comments

fix: enable terminal title control via change_title MCP tool#161

Open
michelhelsdingen wants to merge 19 commits intoslopus:mainfrom
michelhelsdingen:main
Open

fix: enable terminal title control via change_title MCP tool#161
michelhelsdingen wants to merge 19 commits intoslopus:mainfrom
michelhelsdingen:main

Conversation

@michelhelsdingen
Copy link

Summary

  • Add setTerminalTitle() function using OSC escape sequences to set iTerm2/Terminal.app window title when change_title MCP tool is invoked
  • Add CLAUDE_CODE_DISABLE_TERMINAL_TITLE=1 env var to launcher to prevent Claude Code from overwriting the terminal title
  • change_title now updates both the terminal window title (for desktop) and the Happy server (for mobile app)

Context

The change_title MCP tool currently only sends the title to the Happy server for the mobile app. Desktop users (iTerm2, Terminal.app) don't see the title reflected in their terminal tabs/windows. This makes it hard to distinguish between multiple Happy sessions.

This fix uses standard OSC 0 escape sequences (\x1b]0;title\x07) which work with iTerm2, Terminal.app, and most modern terminal emulators.

Test plan

  • Verified title appears in iTerm2 tab/window after change_title is called
  • Verified remote → local mode switch still works without stdin regression
  • Verified Happy mobile app still receives title updates

🤖 Generated with Claude Code
via Happy

michelhelsdingen and others added 19 commits February 6, 2026 13:50
…us#11)

## THE FIX
Forward SIGTERM, SIGINT, SIGHUP to child Claude CLI process.
This ensures child processes are killed when parent is terminated.

## ROOT CAUSE
When switching between local/remote modes, the parent process was killed
but child Claude CLI processes remained alive ("orphaned"). These orphaned
processes continued to hold stdin, causing:
- Duplicate/garbled characters when typing
- "Competing processes" fighting for terminal input

## SOLUTION (from GitHub slopus/happy#430)
```javascript
const forwardSignal = (signal) => {
    if (child.pid && !child.killed) {
        child.kill(signal);
    }
};
process.on('SIGTERM', () => forwardSignal('SIGTERM'));
process.on('SIGINT', () => forwardSignal('SIGINT'));
process.on('SIGHUP', () => forwardSignal('SIGHUP'));
```

## FILES CHANGED
- scripts/claude_version_utils.cjs - binary launcher signal forwarding
- src/claude/claudeLocal.ts - TypeScript launcher signal forwarding

## TESTED
✅ Fresh local mode - typing works
✅ Switch to remote mode - typing works
✅ Switch back to local mode - typing works (was broken)
✅ Multiple mode switches - stable

## IMPORTANT
This is the ONLY fix needed. No stdin cleanup, no removeAllListeners(),
no setRawMode changes required. Signal forwarding alone solves it.

Closes slopus#11
References: slopus/happy#430, PR slopus#127

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
Re-apply terminal title feature (originally df7262c) that was lost
when upstream main diverged. Sets iTerm2/Terminal.app window title
via OSC escape sequences when change_title is invoked.

- Add CLAUDE_CODE_DISABLE_TERMINAL_TITLE=1 to launcher to prevent
  Claude Code from overwriting the terminal title
- Add setTerminalTitle() using OSC 0 escape sequence for direct
  window title control
- change_title now sets both terminal title and Happy app title

Tested: title updates work in iTerm2, remote→local mode switch
still functions correctly (no stdin regression).

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
- Add getUploadDir RPC handler for mobile image uploads
- Support writeFile to session-scoped temp upload directory
- Enhance path security with symlink resolution and multi-dir support
- Pass sessionId to registerCommonHandlers for upload dir scoping
- Remove CLAUDECODE env var before spawning Claude subprocess
- Improve error logging in claudeRemoteLauncher (Error objects → message+stack)

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
…ndlers

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
setTerminalTitle() now checks process.stdout.isTTY before writing OSC
escape sequences. In remote/daemon sessions stdout is a pipe (not TTY),
causing EPIPE which triggered uncaughtException and killed the session.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Wire up real-time Live Activity updates via APNs HTTP/2 pushes.
The CLI now receives the ActivityKit push token from the mobile app
via RPC and sends status updates (running/waiting/permission) with
coordinator task progress to the iOS widget.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
Sessions spawned with detached:true survive daemon restarts but were
previously lost from the in-memory tracking Map. Now the daemon writes
tracked sessions to ~/.happy/tracked-sessions.json on every Map mutation
and recovers still-alive sessions on startup.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
- Fix socket message loss in sendClaudeSessionMessage with queue/retry
- Fix permission mode leak between session switches
- Add markCurrentFailed() to Coordinator for proper failure handling
- Remove APNs Live Activity module (replaced by Expo push)
- Add parseOptions() for extracting actionable push notification buttons
- Enhanced auto-pilot completion notification with failure reporting

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
…and-p0-fixes

feat: session persistence, coordinator, and P0 fixes
Adds automatic session recap posting to the /v1/feed endpoint when
Claude finishes a turn. The recap includes title, duration, cost,
model, and turn count. Uses repeatKey for idempotent updates per session.

- Add postFeedItem() to ApiClient
- Track session title from mcp__happy__change_title calls
- Extract model from result.modelUsage
- Fire-and-forget in onResult (before killSession can terminate process)

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
Three fixes for the tmux-based session spawning and local mode takeover:

1. tmux spawn: Fix flag ordering and env var escaping in spawnInTmux().
   tmux stops parsing options at the first positional argument, so -P,
   -F, -t flags must come before the shell-command. Also remove shell
   escaping from -e values since Node.js spawn() bypasses the shell.

2. local mode crash: Remove CLAUDECODE and CLAUDE_CODE_ENTRYPOINT env
   vars before spawning the local Claude CLI. The SDK sets
   CLAUDE_CODE_ENTRYPOINT='sdk-ts' in process.env during remote mode,
   which persists after remote mode ends. When passed to the local CLI,
   Claude detects nested session and exits immediately (~263ms).

3. diagnostics: Add exit code/signal/runtime logging to claudeLocal,
   detect suspiciously fast exits (<2s) as failures, and implement
   retry logic (max 3 attempts) with error reporting to mobile app.

Also adds token usage fields to session recap events.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
The isReplayPhase logic (added in 147553e) assumed the Claude Code SDK
replays old messages when resuming a session. It does not — only new
turn messages are streamed. This caused all messages to be suppressed
and the first result to be skipped during local→remote handoff, leaving
the Mac terminal stuck on "Starting new Claude session...".

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
Track files read, files modified, bash commands, and tools used during
remote sessions. This data is included in the session-recap push
notification so the phone app can show a richer summary of what happened.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant