Hooks

Four lifecycle hooks declared in hooks/hooks.json. Each invokes subq hooks --provider claude --event <event>.

Event Lifecycle

The hook sequence from session start through shutdown. Each event routes to the SubQ CLI via the command declared in hooks.json.

Hook Lifecycle
SessionStart subq hooks --provider claude --event session_start REPLACE
user works Claude Code tools, edits, tests…
PreCompact subq hooks --provider claude --event pre_compact REPLACE
Stop subq hooks --provider claude --event stop OBSERVE
StopFailure subq hooks --provider claude --event stop_failure OBSERVE

Hook Contracts

Each hook's command, timeout, matcher, status message, and behavior when SubQ is missing.

SESSION START

SessionStart

Command: bash -c 'command -v subq && subq hooks ... || printf fallback'

Timeout: 45,000ms

Matcher: startup|resume

Status: "SubQ: Indexing codebase..."

bash wrapper fallback JSON

STOP

Stop

Command: subq hooks --provider claude --event stop

Timeout: 30,000ms

Matcher: "" (all)

Status: "SubQ: Session handoff..."

bare command silent no-op

PRE COMPACT — DEFERRED

PreCompact

Command: subq hooks --provider claude --event pre_compact

Timeout: 120,000ms

Matcher: "" (all)

Status: "SubQ: Building structured handoff..."

bare command blocking

STOP FAILURE

StopFailure

Command: subq hooks --provider claude --event stop_failure

Timeout: 10,000ms

Matcher: "" (all)

Status: (none)

bare command advisory

Detection & Degradation

What happens at each hook event depending on whether SubQ is installed, missing, or encounters an error.

Event
Installed
Missing
Error
SessionStart
Context injected
Install link
Degraded msg
Stop
Handoff saved
Silent {}
Silent {}
PreCompact
Context preserved
Silent {}
Silent {}
StopFailure
Advisory
Silent {}
Silent {}
Design principle: Only SessionStart surfaces messages to the user. Other hooks degrade silently to avoid spamming every event. Exit code is always 0—degraded output, never a crash.

stdout Format

Hooks return JSON to stdout. Two shapes—inject context or no-op.

Inject Context (replace decision)
{
  "hookSpecificOutput": {
    "hookEventName": "SessionStart",
    "additionalContext": "text injected into Claude's context"
  }
}
No-op (observe decision)
{}

Known Issues

Active bugs and constraints in the hook system.

K-01
SessionEnd hook broken for plugins

Claude Code issue #33458: SessionEnd hooks don't fire from plugins. We use Stop instead.

Blocked
K-02
Stop cooldown required

Stop fires per turn, not per session. A 3-minute per-session cooldown prevents excessive handoff generation.

Workaround
K-03
Timeout budgets vary by event

SessionStart gets 45s for indexing; StopFailure gets only 10s (advisory only). PreCompact has the largest budget at 120s for structured handoff before lossy compaction.

By Design