Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions plugins/core/hooks/hooks.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
{
"hooks": {
"SessionStart": [
{
"matcher": "startup|resume|clear|compact",
Comment on lines +3 to +5
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Remove ended sessions from multiplexing count

This tracking logic only runs on SessionStart and never clears a session marker when a session ends, so closed windows are still counted as "active" for up to 2 hours (or 24 hours until cleanup). In practice, if a user opens 3 sessions, closes 2, and then starts another within 2 hours, the hook still reports 3+ active sessions and injects unnecessary guidance. Adding a SessionEnd hook to delete the corresponding marker would make the count reflect truly active sessions.

Useful? React with 👍 / 👎.

"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/session-multiplexing.sh"
}
]
}
],
"PostToolUse": [
{
"matcher": "TodoWrite",
Expand Down
36 changes: 36 additions & 0 deletions plugins/core/hooks/session-multiplexing.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/bin/bash
# SessionStart hook: Detect multiple active Claude Code sessions
# Touches a session file and counts active sessions (modified in last 2 hours)
# When 3+ active, outputs a reminder to re-ground context in responses

# Read JSON input from stdin
INPUT=$(cat)

# Extract session_id
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty')

if [ -z "$SESSION_ID" ]; then
exit 0
fi

# Session tracking directory
SESSION_DIR="$HOME/.claude/sessions"
mkdir -p "$SESSION_DIR"

# Sanitize session_id to prevent path traversal — use only the basename
SESSION_FILE="$SESSION_DIR/$(basename "$SESSION_ID")"
touch "$SESSION_FILE"

# Count sessions modified in last 2 hours (find -mmin works on both macOS and Linux)
ACTIVE_COUNT=$(find "$SESSION_DIR" -type f -mmin -120 2>/dev/null | wc -l | tr -d ' ')

# Clean up sessions older than 24 hours
find "$SESSION_DIR" -type f -mmin +1440 -delete 2>/dev/null

# Threshold: 3+ sessions is where context-juggling becomes error-prone.
# Below 3, a user can reasonably track what each window is doing.
if [ "$ACTIVE_COUNT" -ge 3 ]; then
echo "Multi-session mode: $ACTIVE_COUNT active Claude Code sessions detected. The user is juggling multiple windows — always include explicit context (repo name, branch, what we're working on) when reporting status or completing work."
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hook outputs plain text instead of required JSON format

High Severity

The echo on line 33 outputs a plain text string, but Claude Code's SessionStart hooks expect JSON output containing a hookSpecificOutput with an additionalContext field to inject context into the conversation. Plain text stdout from a SessionStart hook is not treated as conversation context — it triggers a "hook output does not start with {" warning and may produce a "startup hook error" in the UI. The message never reaches Claude, which defeats the entire purpose of the hook. The output needs to be structured as JSON, e.g. {"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"..."}}.

Fix in Cursor Fix in Web

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

WONTFIX — incorrect. For SessionStart hooks, stdout is injected directly as context. The Claude Code docs example literally uses echo 'Reminder: ...' plain text. JSON hookSpecificOutput/additionalContext is only required for decision-returning hooks like PreToolUse. The plain echo here is the correct pattern.

fi

exit 0
Loading