Session lifecycle hooks: proactive Claude response on start + pre-exit turns
Preflight Checklist
- [x] I have searched existing requests and this feature hasn't been requested yet
- [x] This is a single feature request (not multiple features)
Problem Statement
Claude Code has no mechanism to let Claude act autonomously at session boundaries: —
neither at the start (before any user input) nor at the end (before the process exits).
Today, Claude only generates output in response to a user turn. This means:
- At session start: useful context injected by
SessionStarthooks (previous session summary, open issues/PRs, collaborator changes, system status) is available in Claude's system context, but Claude cannot surface it until the user types something. The user must manually trigger a response every session and if that trigger is not performed that session will not cover the actual needs of the project during that session, just the human being concerns at that moment based on their, therefore, imprecise memory, not an actually complete information.
- At session end: if the user closes the terminal or runs /exit without first typing a trigger phrase, any configured end-of-session actions (writing a summary, flushing commits, posting a log) never run. The existing
SessionEndhook only triggers shell commands — Claude cannot act. Again, in the same way, we are not fulfilling the main goal of the agent, supporting the human being on their imperfect actions the way they will always have what they need at session start.
Both gaps share the same root cause and block the same class of user-configured, and human being needed automated sessions workflow. We are requesting the two primitives needed to close that loop:
Preparing, before leaving the session, what will be needed right when starting the next session protecting the humans from their interactions.
Proposed Solution
Primitive 1 — Proactive response on session start
A mechanism for Claude to generate and display an assistant turn at session start, before the user types anything, using context already injected by the SessionStart hook.
Suggested implementation: if the SessionStart hook returns an "autoPrompt" field in its JSON response, Claude Code treats that string as a synthetic first user message, processes it, and displays Claude's response immediately.
Example hook response:
{"continue": true, "autoPrompt": "Show the session briefing from context."}
Alternatively: a dedicated hook event SessionStartPrompt whose stdout is treated as the first user message.
Primitive 2 — Pre-exit hook that triggers a Claude turn
A PreSessionEnd hook event that fires when the user requests exit — before termination — and injects a prompt into Claude's context so Claude can execute configured actions before the session closes.
Required behavior:
- Fires on /exit, Ctrl+D, and terminal close (not only one of them).
- Hook receives a JSON payload: session_id, transcript_path, cwd.
- Hook's stdout (or a designated JSON field) is injected as a final prompt that Claude processes normally — Claude's response and tool calls complete before the process exits.
- Optional: non-zero hook return code blocks the exit (analogous to Git's pre-commit hook), with /exit --force as override.
This is explicitly different from the existing advisory SessionEnd hook, which fires after termination begins and cannot trigger a Claude turn.
Alternative Solutions
We have tested every workaround we could find. None achieves the goal.
For session start (Primitive 1):
SessionStarthook stdout → Claude's system context: works, but Claude only acts on it when the user sends a message. The information is there; Claude cannot surface it proactively.- Hook stdout visible in the terminal: was working in v2.1.15 and v2.1.25, regressed in v2.1.27 (see #23524, #24425), partially re-appeared in v2.1.157, regressed again in v2.1.158 (see #64119, still open). Even when it worked, it showed raw text in the terminal — not a Claude response in the conversation UI.
claude --print --continuevia shell alias: re-enters the prior session with high latency and wrong UX (replays prior conversation instead of opening fresh with a briefing).- Writing to /dev/tty from the hook script: produces terminal output before Claude starts, but outside the conversation context — Claude cannot reference or act on it.
expect-based synthetic keypress: fragile, terminal-dependent, requires a separate process.
For session end (Primitive 2):
- Existing
SessionEndhook: fires after termination begins — Claude cannot act. Also unreliable: does not fire on /exit in some versions (#17885, #35892) and is killed before async work completes (#41577). Stophook: fires after each Claude turn during the session, not at exit.- Shell wrapper scripts around the
claudebinary: can run commands after exit but cannot trigger a Claude turn or access final session state cleanly.
Priority
High - Significant impact on productivity
Feature Category
Configuration and settings
Use Case Example
Concrete workflow we are trying to automate (currently requires manual intervention at both ends of every session):
SESSION START:
- SessionStart hook injects: previous session summary, open GitHub issues/PRs, recent changes from collaborators via a shared interface folder.
- With Primitive 1: Claude immediately displays a structured briefing — "Last session: X. Open PRs: Y. Collaborator added endpoint Z." — before the user types anything.
- Without Primitive 1: all that context sits silently in Claude's system prompt. The user must type "show me the briefing" every single session.
SESSION END:
- User runs /exit or closes the terminal.
- With Primitive 2: PreSessionEnd hook fires, injects "Write session summary to .claude/session_summary.md and commit pending changes." Claude executes, the file is written, the commit is made, then the session exits.
- Without Primitive 2: if the user forgets to type "I'm closing the session", no summary is written and the next session starts without continuity context.
The two primitives together close the loop completely: end of session N automatically produces the context that start of session N+1 automatically surfaces — with no user input required at either boundary.
This pattern generalizes to any user-configured session workflow:
- Surface CI/deploy status at start, post session log at end.
- Check for unread collaborator messages at start, flush pending commits at end.
- Load project-specific context at start, archive transcript at end.
Additional Context
Related issues — none covers exactly this request:
| # | Title | Status | Relationship |
|--------|---------------------------------------------------------------|------------------------|--------------|
| #47023 | Expose compact/session lifecycle hooks for memory layers | Open | Closest precedent; focuses on shell hooks, not Claude turns |
| #64119 | Hook stdout shown + suppressOutput:true ignored in 2.1.158 | Open | Symptom of unstable hook output visibility history |
| #12755 | Add blocking pre-exit hook | Closed — bot, inactive | Primitive 2 partial; incorrectly auto-closed as dup of #9293 (different proposals) |
| #9293 | Pre-exit hook (advisory) | Closed — bot, dup | Advisory only; no Claude turn; wrongly marked dup of #2622 |
| #41577 | SessionEnd hooks killed before async completion | Closed — bot, dup | Shows existing SessionEnd unreliable for non-trivial work |
| #17885 | SessionEnd hook doesn't fire on /exit | Closed — bot, dup | Shows existing SessionEnd doesn't cover all exit paths |
| #23524 | SessionStart hook stdout not displayed (regression) | Closed — stale | Primitive 1 partial; raw terminal output, not a Claude response |
Note on #12755: the auto-duplicate bot flagged it as a duplicate of #9293, but the author's comment explains why they are different (advisory vs. blocking).
Neither was ever reviewed by a human. We are not re-filing #12755 — our request goes further (Claude turn, not just shell command).
Showing cached comments. Read the full discussion on GitHub ↗
4 Comments
I would avoid modeling this as an unmarked synthetic user message. Session-boundary turns need their own provenance and idempotency.
Useful fields for both primitives:
source:session_start_auto | pre_session_endran | skipped | failedThat prevents duplicate start briefings after resume and makes pre-exit work auditable. For
PreSessionEnd, I would also make timeout and force-exit behavior explicit in the transcript so a blocked exit is explainable later instead of looking like the session simply hung.---
_Generated with ax._
The absence of session-boundary turns is a real gap for autonomous / unattended workflows. Right now the only "proactive" thing a session can do at start is respond to whatever context the SessionStart hook injected -- it cannot surface that context, ask a clarifying question, or run a pre-flight check before the user types anything. And at exit there is no clean path to write a summary, checkpoint work in progress, or notify a coordinator that the session is ending.
The idempotency and provenance points from the other comment are right. A session-boundary turn needs to be distinguishable from a normal user turn -- both for the model (so it does not confuse a system-injected briefing with user input) and for external tooling (so a coordinator can tell "the agent ran its startup briefing" from "the user typed something").
A few specific use cases where this matters in practice:
Session start: an unattended agent needs to check its own state before accepting tasks. "What did I complete last session? What is still open? Are the files I expect to exist actually there?" Today this requires the user to type that prompt, or inject it via a hook -- but the agent cannot ask those questions itself before the user engages.
Pre-exit: a long-running background agent should write a handoff note before it exits -- "I got through items 1-7, item 8 is blocked on X, the relevant file is Y." Today there is no hook that runs a model turn before exit; the best you can do is a shell hook that writes a static file.
Scheduled routines: an agent that runs on a cron-style schedule needs to know "what time is it, what is my current task list, what changed since I last ran" at session start, before any interactive input. The SessionStart hook can inject that data but the agent cannot act on it autonomously.
Building session coordination tooling on top of Claude Code (claudeverse.ai -- external session polling and handoff) and the lack of an autonomous session-start turn is one of the top gaps we have had to work around with external orchestration.
This maps to a real workflow gap for anyone running multiple Claude Code sessions across a day. Right now the session boundary is a hard stop -- whatever state the agent accumulated has to be manually handed forward, and anything not captured in CLAUDE.md is just gone.
What I have found building a polling scheduler on top of Claude Code: the missing piece isn't just the hook primitive, it's a durable record of "what did this session actually do and decide" that the next session can read without the user summarizing it by hand. The autoPrompt field you proposed in SessionStart is the right shape -- the question is what writes into it. A structured session journal (not just the raw transcript) would make the cross-session continuity genuinely reliable rather than dependent on the agent remembering to summarize.
The PreSessionEnd hook is the harder one because it has to survive terminal kills. Have you run into cases where Ctrl+C mid-task means the summary never gets written? That is the failure mode I keep hitting -- the session ends uncleanly and the next one starts with no context.
(Building tooling in this space at claudeverse.ai -- happy to share notes on what the scheduler layer sees from the outside if it would be useful for the feature design.)
Adding a Cowork data point for this request — it maps almost exactly onto your two primitives, and Cowork makes Primitive 2 the only viable path.
I maintain a Claude Code plugin (
swe) used inside Cowork Desktop on Windows (Cowork VM, July 2026). The plugin renders a rich "session card" at both session open and session close.Primitive 1 (proactive response on start) — already works for us in Cowork.
Our plugin's
SessionStarthook (plugin-scoped,hooks/hooks.json) fires in Cowork and injects an instruction into context ("the card is pre-rendered, display it now"). The instance reliably shows the opening card as its first output, before I type anything. So on Cowork/Windows, aSessionStartplugin hook that injects text is acted upon at the first turn — this is exactly your Primitive 1, and it is deterministic for us. (Note: this partly updates #27398, which reports pluginhooks/hooks.json"never firing" in Cowork — for usSessionStartdoes fire as of July 2026.)Primitive 2 (pre-exit Claude turn) — this is the gap, and it's worse in Cowork.
At session close (our
/swe:cycle//swe:endcommands) there is no equivalent event that deterministically injects an instruction Claude executes as its first output. Every Cowork-available workaround fails:SessionEnd: fires after termination, stderr only → cannot drive a Claude turn (matches your analysis).UserPromptSubmit: does not fire in Cowork (see #63360); our hook guards to a no-op there accordingly, so it never sees the closing command.UserPromptExpansion(matcher on the slash-command name): the docs don't confirm it runs in Cowork, and in practice it does not reliably inject at command invocation.Result: the closing card is non-deterministic — sometimes skipped (instance proceeds in text), sometimes the instance says "I'll show it" but
show_widgetrenders nothing on screen, sometimes only a partial card appears.Why this strengthens the request: your
PreSessionEndprimitive (a pre-exit event that triggers a Claude turn) is exactly what's missing. In Cowork it's the only path, because the CLI-oriented workarounds you list (stdin-readingUserPromptSubmit, terminal stdout,/dev/tty) don't exist in the sandbox. APreSessionEnd— or a documented, Cowork-supported way to inject a deterministic first-output instruction on slash-command invocation — would close the loop for plugins that render a session-boundary UI.One Cowork-specific question: is
show_widget, when called by the instance during the closing turn, expected to be reliable? We observe cases where the instance reports calling it but no widget appears.Environment: Cowork Desktop, Windows 11 (MSIX), Cowork VM; plugin
swewithhooks/hooks.json(SessionStartworks;UserPromptSubmit/UserPromptExpansiondo not). Related Cowork hook issues: #63360 (user-scope hooks in Cowork), #27398 (pluginhooks/hooks.jsonfiring in Cowork), #51420 (plugin-scoped hook goes silent mid-session).