/clear changes session_id in hook input without re-running SessionStart hooks, breaking session-aware PreToolUse hooks
Summary
When /clear is run, regenerateSessionId() assigns a new UUID to the session. Subsequent PreToolUse hook invocations receive this new session_id in their JSON input. However, SessionStart hooks do not re-run after /clear, so any state created by SessionStart (temp directories, env vars written to CLAUDE_ENV_FILE, permits) is keyed to the original session ID.
This means hooks that validate paths against session_id (e.g., checking that a file is within /tmp/claude-session-{session_id}/) silently break after /clear -- the hook computes an expected path from the new session ID that doesn't match the actual path created under the original session ID.
Reproduction
- Create a SessionStart hook that creates a session-scoped temp directory from
session_idand exports it viaCLAUDE_ENV_FILE. - Create a PreToolUse hook (matcher:
Read|Write|Edit, if:Read(/tmp/*)) that validates/tmp/paths are within/tmp/claude-session-{session_id}/. - Start a session. SessionStart creates
/tmp/claude-session-{UUID-A}/and sets/tmp/claude-session-cb294609-d25f-4354-bc59-cda9ede72fdb. - Write a file via Bash:
echo test > /tmp/claude-session-cb294609-d25f-4354-bc59-cda9ede72fdb/data.json(succeeds). - Read the file:
Read(/tmp/claude-session-cb294609-d25f-4354-bc59-cda9ede72fdb/data.json)(succeeds -- hook seessession_id= UUID-A, path matches). - Run
/clear. - Read the same file:
Read(/tmp/claude-session-{UUID-A}/data.json)-- hook now receivessession_id= UUID-B (the regenerated ID). The hook computes expected dir as/tmp/claude-session-{UUID-B}/, path doesn't match, hook denies. cat /tmp/claude-session-{UUID-A}/data.jsonvia Bash still succeeds (Bash tool doesn't go through the Read hook).
Root cause
Three things interact:
/clearregenerates the session ID.regenerateSessionId()is called during/clear, andcreateBaseHookInput()usesgetSessionId()to populatesession_id, so all subsequent hooks see the new ID.
- SessionStart hooks don't re-run after
/clear. They are explicitly skipped for non-startup scenarios. The/tmp/claude-session-cb294609-d25f-4354-bc59-cda9ede72fdbenv var persists in Bash tool commands (viaCLAUDE_ENV_FILE), but the hook infrastructure has no equivalent mechanism.
- Session env vars are not available in hook subprocesses. Hooks inherit
process.envviasubprocessEnv(), which does not include vars written toCLAUDE_ENV_FILE. So a hook can't read/tmp/claude-session-cb294609-d25f-4354-bc59-cda9ede72fdbto find the correct directory -- it can only derive it fromsession_id, which is now wrong.
The parent session ID is stored internally (STATE.parentSessionId) but is not included in hook input, so hooks have no way to recover the original session's ID.
(Source references based on the leaked source from 2026-03-31, roughly corresponding to CLI version 2.1.x. Line numbers may have drifted.)
Impact
Any PreToolUse hook that uses session_id to validate or construct paths breaks silently after /clear. The same issue likely affects --continue and --resume, which also skip SessionStart hooks.
Possible fixes
- Include session env vars in hook subprocess environment -- source
CLAUDE_ENV_FILEcontent into theenvpassed tospawn()so hooks can read/tmp/claude-session-cb294609-d25f-4354-bc59-cda9ede72fdbdirectly. - Add a
process_session_idfield to hook input that never changes for the lifetime of the process (the original session ID from startup), distinct from the conversation-scopedsession_id. - Include
parent_session_idin hook input -- let hooks check both current and parent session dirs. Doesn't solve multiple/clears in a row. (Previously requested in #29094, closed as stale.) - Re-run SessionStart hooks after
/clearwith the new session ID.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗