Skip to content

feat(headless): wire lifecycle hooks (Stop, SessionStart, SessionEnd, UserPromptSubmit)#1974

Open
SARP4 wants to merge 1 commit intoletta-ai:mainfrom
SARP4:fix/lifecycle-hooks-headless-mode
Open

feat(headless): wire lifecycle hooks (Stop, SessionStart, SessionEnd, UserPromptSubmit)#1974
SARP4 wants to merge 1 commit intoletta-ai:mainfrom
SARP4:fix/lifecycle-hooks-headless-mode

Conversation

@SARP4
Copy link
Copy Markdown

@SARP4 SARP4 commented Apr 26, 2026

Summary

Lifecycle hooks (Stop, UserPromptSubmit, SessionStart, SessionEnd) only fired in the interactive CLI (App.tsx). This PR wires them into headless mode so SDK consumers (lettabot, PM2, etc.) can use hooks for analytics, logging, policy enforcement, and external integrations.

Fixes #1282

Problem

As described in #1282, headless mode had zero integration for lifecycle hooks. Tool hooks (PreToolUse, PostToolUse, PostToolUseFailure) already worked because they execute via tools/manager.ts regardless of mode. But lifecycle hooks were only called from App.tsx.

Changes

src/headless.ts

  • Import runStopHooks, runSessionStartHooks, runSessionEndHooks, runUserPromptSubmitHooks
  • SessionStart (both modes): fire-and-forget after agent resolution
  • Stop (bidirectional): run after end_turn, with blocking semantics — if exit code 2, inject <stop-hook> feedback and continue conversation loop
  • Stop (one-shot): run after end_turn, with blocking semantics — if exit code 2, continue turn loop with hook feedback
  • SessionEnd (both modes): fire-and-forget before exitHeadless / exitBidirectional
  • UserPromptSubmit (bidirectional): run after parsing user input — if blocked, emit error event and skip

All hook extraction patterns match App.tsx exactly.

Testing

  • 88 headless unit tests pass (0 fail)
  • 27 hook executor tests pass (0 fail)
  • Live tested:
    • SessionStart ✅
    • Stop ✅
    • SessionEnd ✅
    • UserPromptSubmit ✅

Checklist

  • Build passes (bun run build)
  • Headless tests pass
  • Hook executor tests pass
  • Live tested in bidirectional (stream-json) mode
  • Follows App.tsx hook patterns exactly

@SARP4 SARP4 marked this pull request as ready for review April 26, 2026 01:19
@cpacker cpacker self-assigned this Apr 26, 2026
@SARP4 SARP4 force-pushed the fix/lifecycle-hooks-headless-mode branch from 684a0e8 to 1994a2e Compare April 26, 2026 03:54
… UserPromptSubmit)

Lifecycle hooks (Stop, UserPromptSubmit, SessionStart, SessionEnd) only fired
in interactive CLI (App.tsx). This wires them into headless mode so SDK
consumers (lettabot, PM2) can use hooks for analytics, logging, and external
integrations.

Changes:
- Import runStopHooks, runSessionStartHooks, runSessionEndHooks,
  runUserPromptSubmitHooks from ./hooks
- SessionStart: fire-and-forget after agent resolution
- Stop (bidirectional): run after end_turn, with blocking semantics
  (exit 2 → inject <stop-hook> feedback and continue conversation loop)
- Stop (one-shot): run after end_turn, with blocking semantics
  (exit 2 → continue turn loop with hook feedback)
- SessionEnd: fire-and-forget before exitHeadless/exitBidirectional
- UserPromptSubmit (bidirectional): run after parsing user input,
  blocked → emit error event and skip

All hook extraction patterns match App.tsx exactly.
Tested: 88 headless tests pass, 27 hook executor tests pass.

Fixes letta-ai#1282
@SARP4 SARP4 force-pushed the fix/lifecycle-hooks-headless-mode branch 2 times, most recently from 92d4889 to 736afe0 Compare April 26, 2026 04:16
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.

feat: add lifecycle hooks (Stop, SessionStart, SessionEnd, UserPromptSubmit) to headless mode

2 participants