Skip to content

Conversation

@Scoteezy
Copy link
Contributor

Session Hook Tracking

Reliable Claude session ID tracking via native hooks.

How It Works

  1. Hook Server starts on a random local port when Happy launches
  2. Settings File is generated with SessionStart hook pointing to the server
  3. Claude receives --settings <path> flag and fires hook on session start
  4. Hook Forwarder script reads stdin from Claude and POSTs to Happy's server
  5. Happy updates internal session ID and notifies all components
┌─────────┐  stdin   ┌──────────────────┐  HTTP POST   ┌─────────────┐
│  Claude │ ──────▶  │ hook_forwarder   │ ──────────▶  │ Hook Server │
└─────────┘          └──────────────────┘              └─────────────┘
                                                              │
                                                              ▼
                                                       Session updated

Files

File Purpose
src/claude/utils/startHookServer.ts HTTP server receiving session hooks
src/claude/utils/generateHookSettings.ts Creates temp settings JSON for Claude
scripts/session_hook_forwarder.cjs Forwards stdin to HTTP server

Why

Claude changes session ID on --resume, --continue, and /compact. Previously Happy lost track of the active session. Now Claude notifies Happy directly via hooks.

- Add dedicated HTTP server (startHookServer.ts) for receiving Claude session hooks
- Generate temporary settings file with SessionStart hook configuration
- Pass --settings flag to Claude CLI for hook integration
- Update Session class with callback mechanism for session ID changes
- Connect scanner to session callbacks for real-time session file tracking
}

// Add hook settings for session tracking
if (opts.hookSettingsPath) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we just pass this always? update opts to must include this - less branching == better

logger.debug(`[START] Session hook received: ${sessionId}`, data);

// Update session ID in the Session instance
if (currentSession) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm how is this going to work? What if we don't have a session?

Also dynamic import - lets not do that for the type above :D


/**
* Consume one-time Claude flags from claudeArgs after Claude spawn
* Currently handles: --resume (with or without session ID)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

elaborate on what the responsibility of this code is

- hookSettingsPath now required (less branching)
- Static import instead of dynamic for Session type
- JSDoc for onSessionFound callback
- Remove fake session ID generation
- Add control flow docs in startHookServer.ts
@bra1nDump bra1nDump merged commit 35b567d into slopus:main Dec 22, 2025
4 checks passed
ahundt added a commit to ahundt/happy-cli that referenced this pull request Dec 30, 2025
Summary: Integrates PR slopus#99 (hook-based session tracking) and PR slopus#98
(Gemini/ACP integration) while preserving all offline mode and robustness
features from the feature branch, plus DRY improvements.

What changed:
- Session hook server system for reliable Claude session ID capture
- Gemini backend as alternative to Claude/Codex with ACP integration
- Fixed memory leak in session.ts (stored interval reference + cleanup())
- Added socket connection check in apiSession.ts
- Created shared offlineSessionStub.ts utility (DRY refactor)
- Added gemini to profile compatibility validation
- Preserved: offline mode, 5xx handling, profile system, tmux integration

Files affected:
- src/claude/runClaude.ts: Hook server + offline fallback integration
- src/claude/session.ts: RAII cleanup, callback system, hookSettingsPath
- src/claude/loop.ts: hookSettingsPath threading
- src/claude/claudeLocal.ts: Hook parameters
- src/claude/claudeRemote.ts: mapToClaudeMode() preserved
- src/api/api.ts: Graceful 5xx handling preserved
- src/api/apiSession.ts: Socket check restored
- src/daemon/run.ts: Gemini added to agent selection
- src/persistence.ts: Added gemini to ProfileCompatibilitySchema
- src/utils/offlineSessionStub.ts: New shared DRY utility
- src/codex/runCodex.ts: Updated to use shared offline stub
- src/gemini/runGemini.ts: Added offline mode with shared stub
- src/agent/*, src/gemini/*: New from main (no conflicts)
- scripts/session_hook_forwarder.cjs: Hook forwarder from main

Testable: npm test (266 pass, 8 fail - baseline unchanged)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants