|
| 1 | +# Claude Code hooks |
| 2 | + |
| 3 | +Two hooks that connect a Claude Code session to your Second Brain: one recalls |
| 4 | +project context when a session opens, one saves the conversation when it closes. |
| 5 | + |
| 6 | +They are independent of the MCP server. Use either, or both. |
| 7 | + |
| 8 | +## What the hooks do |
| 9 | + |
| 10 | +| Event | Runs on | Action | Cost | |
| 11 | +|---|---|---|---| |
| 12 | +| `SessionStart` | `startup`, `clear`, `compact` | `GET /recall` for this project, prints up to 5 memories into the session | one recall (~1 s), none on compaction | |
| 13 | +| `SessionEnd` | every reason (`clear`, `resume`, `logout`, `prompt_input_exit`, `other`) | `POST /capture` with the tail of the conversation | one capture (embedding + often a model call), 30 s hook timeout | |
| 14 | + |
| 15 | +`resume` and `fork` are skipped on start: those transcripts already contain the |
| 16 | +earlier injection. `compact` is not skipped — compaction discards it. |
| 17 | + |
| 18 | +On `startup` and `clear` the block that was printed is cached under |
| 19 | +`$XDG_CACHE_HOME/second-brain/session-<session_id>.txt` (`~/.cache/…` by |
| 20 | +default). Compaction re-prints that file verbatim and makes no request at all: |
| 21 | +the session id survives compaction and rotates on `/clear`, so a cached block is |
| 22 | +always the current session's context. With no cache, or one older than 24 h, |
| 23 | +compaction falls back to a live recall. |
| 24 | + |
| 25 | +## Install, upgrade, check, uninstall |
| 26 | + |
| 27 | +```bash |
| 28 | +bash install.sh https://your-worker.workers.dev your-token # install or upgrade |
| 29 | +bash install.sh # reuse existing credentials, or prompt |
| 30 | +bash install.sh --check # prove the hooks reach the Worker |
| 31 | +bash install.sh --uninstall # remove only our entries |
| 32 | +``` |
| 33 | + |
| 34 | +PowerShell, for Windows without Git Bash — same behaviour, same guarantees: |
| 35 | + |
| 36 | +```powershell |
| 37 | +.\install.ps1 -WorkerUrl https://your-worker.workers.dev -Token your-token |
| 38 | +.\install.ps1 # reuse existing credentials, or prompt |
| 39 | +.\install.ps1 -Check |
| 40 | +.\install.ps1 -Uninstall |
| 41 | +``` |
| 42 | + |
| 43 | +Re-running is safe: the installer replaces its own entries in |
| 44 | +`~/.claude/settings.json` and preserves everything else. It refuses to write a |
| 45 | +settings file that is not valid JSON rather than overwriting it. |
| 46 | + |
| 47 | +**Restart any session that is already open.** Claude Code snapshots the hook |
| 48 | +config at startup, so a running session keeps the old wiring. |
| 49 | + |
| 50 | +## Where credentials live |
| 51 | + |
| 52 | +`~/.config/second-brain/config.json` (mode 600) — the same file the CLI and the |
| 53 | +desktop app use: |
| 54 | + |
| 55 | +```json |
| 56 | +{ "workerUrl": "https://your-worker.workers.dev", "authToken": "…" } |
| 57 | +``` |
| 58 | + |
| 59 | +Nothing is written into `settings.json` and nothing is passed on the hook |
| 60 | +command line, so the token never appears in Claude Code's settings or in `ps`. |
| 61 | +`SECOND_BRAIN_URL` and `SECOND_BRAIN_TOKEN` in the environment take precedence |
| 62 | +when set. |
| 63 | + |
| 64 | +## What is sent |
| 65 | + |
| 66 | +Recall: |
| 67 | + |
| 68 | +``` |
| 69 | +GET /recall?query=<project>+decisions+and+context&topK=5&workspace=personal&tag=<project> |
| 70 | +``` |
| 71 | + |
| 72 | +with a `tag`-less second attempt if the tagged one returns nothing. With no |
| 73 | +project (a session opened in `$HOME`), one generic query limited to the last 14 |
| 74 | +days is sent instead. |
| 75 | + |
| 76 | +Capture: |
| 77 | + |
| 78 | +```json |
| 79 | +{ |
| 80 | + "content": "Claude Code session <id> — <project>@<branch> — <date> (<reason>)\n\nUser: …\n\nAssistant: …", |
| 81 | + "source": "claude-code", |
| 82 | + "tags": ["<project>"], |
| 83 | + "workspace": "personal" |
| 84 | +} |
| 85 | +``` |
| 86 | + |
| 87 | +Before it is sent, the formatted body — header included — is scanned for |
| 88 | +credentials, and each one is replaced with `[redacted]`: your own configured |
| 89 | +token wherever it appears, `Bearer <token>` values, provider key shapes (`sk-`, |
| 90 | +`ghp_`/`gho_`, `github_pat_`, `xoxb-`/`xoxp-`, AWS `AKIA…`, Google `AIza…`), |
| 91 | +whole PEM private-key blocks, and `TOKEN=`/`SECRET=`/`PASSWORD=`/`API_KEY=` |
| 92 | +style assignments. Only those shapes: a UUID, a commit SHA, a file path and |
| 93 | +ordinary prose are left exactly as they were, because a memory redacted into |
| 94 | +uselessness is worse than no memory. Tool output — where secrets usually live — |
| 95 | +never reaches the body in the first place. |
| 96 | + |
| 97 | +The transcript is read backwards from the end until three human turns are in |
| 98 | +hand (1 MB ceiling), and only human-readable turns survive: `tool_use`, |
| 99 | +`tool_result` and `thinking` blocks, sidechain (subagent) lines, `isMeta` lines, |
| 100 | +compaction summaries and harness noise such as `<system-reminder>` or |
| 101 | +`<command-name>` are all dropped. The body is capped at 2000 characters, newest |
| 102 | +turns first. |
| 103 | + |
| 104 | +Set `SECOND_BRAIN_WORKSPACE=company` to write to the shared layer instead. |
| 105 | +Set `SECOND_BRAIN_DRY_RUN=1` to print the capture body instead of sending it. |
| 106 | + |
| 107 | +## The gate, and the Worker version |
| 108 | + |
| 109 | +A session is captured only when it contains at least one human turn of 40+ |
| 110 | +characters and 200+ characters of conversation (the header does not count) — a |
| 111 | +two-word prompt and a wall of tool output is not a session worth keeping. |
| 112 | + |
| 113 | +Capture also requires **Worker 3.0 or newer** (`GET /health` reports the |
| 114 | +version, cached for 24 h). Against an older brain, recall still works and the |
| 115 | +capture is skipped with one notice per day. Deploy the Worker, then use the |
| 116 | +hooks. |
| 117 | + |
| 118 | +Opt out of either half: |
| 119 | + |
| 120 | +```bash |
| 121 | +SECOND_BRAIN_HOOK_RECALL=0 # no recall on session start |
| 122 | +SECOND_BRAIN_HOOK_CAPTURE=0 # no capture on session end |
| 123 | +``` |
| 124 | + |
| 125 | +## Overlap with the MCP instructions |
| 126 | + |
| 127 | +`AI_Instructions/CLAUDE_INSTRUCTIONS.md` already tells the model to call `recall` |
| 128 | +at the start of every conversation. If you use those instructions with the MCP |
| 129 | +server, the SessionStart hook is a second, unprompted recall on the same topic. |
| 130 | +It is still useful — it runs before the first token and cannot be skipped — but |
| 131 | +if you would rather have only one, set `SECOND_BRAIN_HOOK_RECALL=0` and leave the |
| 132 | +MCP rule in place. |
| 133 | + |
| 134 | +The SessionEnd capture has no MCP equivalent and does not overlap with anything. |
| 135 | + |
| 136 | +## Failure lines you will see |
| 137 | + |
| 138 | +Hooks report failures on stderr and exit non-zero; Claude Code hides stderr from |
| 139 | +a hook that exits 0, which is why nothing is silent any more. |
| 140 | + |
| 141 | +| Line | Meaning | |
| 142 | +|---|---| |
| 143 | +| `[Second Brain] recall failed: HTTP 401 unauthorized — token rejected…` | the token is wrong or was rotated — re-run `install.sh` | |
| 144 | +| `[Second Brain] recall failed: HTTP 404 — is SECOND_BRAIN_URL / workerUrl the Worker origin?` | the URL points at something that is not the Worker root | |
| 145 | +| `[Second Brain] recall failed: no reply within 15s` | the Worker did not answer in time | |
| 146 | +| `[Second Brain] session capture failed: …` | same causes, on the capture call | |
| 147 | +| `SessionEnd hook [<cmd>] failed: …` | Claude Code's own wrapper around the line above | |
| 148 | +| `[Second Brain] session capture needs Worker 3.0+ …` | the brain has not been redeployed to v3; shown once a day | |
| 149 | + |
| 150 | +Nothing here blocks the session. A failed hook costs you the recall or the |
| 151 | +capture, not the conversation. |
| 152 | + |
| 153 | +## Windows |
| 154 | + |
| 155 | +The hooks run under Git Bash if it is installed; without it Claude Code falls |
| 156 | +back to PowerShell, where `install.sh` will not run. Use `install.ps1` there — |
| 157 | +it writes the same credentials file and the same `settings.json` entries, and |
| 158 | +Node does the JSON editing in both installers so the two cannot drift. The hook |
| 159 | +scripts themselves are plain Node and work either way once they are in |
| 160 | +`settings.json`. |
| 161 | + |
| 162 | +The credentials file is written with mode 600, which NTFS ignores; on Windows it |
| 163 | +is protected by the permissions of your user profile directory like any other |
| 164 | +file under `%USERPROFILE%`. |
0 commit comments