open-context open-context
September 3, 2026 · 7 min read

Using Claude Code Hooks to Auto-Save Project Context

Claude Code hooks let you run a script at fixed points in a session — before context gets compacted, when a session ends — so you can call open-context's REST API and save a summary automatically, without waiting for Claude to remember to call save_context itself. Wire a PreCompact or SessionEnd hook to POST /api/contexts and every session leaves a record behind, whether or not anyone asked it to.

Key takeaways

  • Claude Code hooks are shell commands (or HTTP calls, MCP tool calls, prompts, or subagents) that fire on lifecycle events like PreCompact, SessionEnd, and Stop.
  • Each hook receives a JSON payload on stdin, including a transcript_path pointing at the full session transcript — enough to build a real summary, not a guess.
  • A hook can call open-context's REST endpoint directly (POST /api/contexts) with curl, with no MCP tool call and no Claude decision involved.
  • PreCompact is the event that matters most for context capture — it fires right before older messages would otherwise be lost to compaction.
  • Hooks are a safety net, not a replacement for the save_context MCP tool — use both, since one is automatic and the other is deliberate.

The gap: memory that depends on Claude remembering to save it

open-context's MCP server gives Claude six tools — save_context, recall_context, list_contexts, search_contexts, update_context, and delete_context — that let it write and read memory mid-conversation. That works well when you explicitly say "remember this." It works less well for everything you didn't think to say out loud: the three false starts before you landed on an approach, the constraint someone mentioned in passing an hour ago, the decision that got made and then never repeated. If Claude doesn't call save_context, none of that survives past the session, no matter how good the tool is.

Hooks close that gap from the other direction. Instead of relying on a tool call Claude has to decide to make, a hook fires deterministically at a point in the session lifecycle and runs regardless of what Claude was thinking about at the time.

What a Claude Code hook actually is

Hooks are configured in settings.json (project or user level) under a hooks key, one entry per event name. Each entry has a matcher — for tool events like PreToolUse that matches a tool name, for lifecycle events like PreCompact it's typically * — and a list of handlers to run. The simplest and most common handler type is command: a shell script or binary that gets the event's data piped to it as JSON on stdin.

Claude Code fires hooks on dozens of events, but a handful matter for context capture specifically:

EventFires when
StopClaude finishes responding to a single turn
PreCompactRight before Claude Code compacts older context out of the window
SessionEndThe session terminates
SessionStartA session begins or resumes

Stop fires too often to be useful for a whole-session summary — it runs after every single turn. PreCompact is the interesting one: it's the exact moment Claude Code is about to discard detail you might still want, which makes it the natural trigger for "save what matters before it's gone."

Wiring a hook to open-context's REST API

Every hook receives a common JSON payload — session_id, cwd, hook_event_name, and a transcript_path pointing at the session's JSONL transcript on disk. A command hook can read that file, pull out the assistant's recent messages, and post them straight to open-context's HTTP server with nothing more exotic than curl and jq.

The hook script

#!/bin/bash
# .claude/hooks/save-context.sh
INPUT=$(cat)
TRANSCRIPT=$(echo "$INPUT" | jq -r '.transcript_path')

SUMMARY=$(tail -n 60 "$TRANSCRIPT" \
  | jq -rs '[.[] | select(.type=="assistant")
             | .message.content[]? | select(.type=="text") | .text]
            | join("\n\n")' \
  | tail -c 4000)

if [ -n "$SUMMARY" ]; then
  curl -s -X POST http://localhost:3000/api/contexts \
    -H "Content-Type: application/json" \
    -d "$(jq -n --arg content "$SUMMARY" \
          '{content: $content, tags: ["auto-saved", "hook"], source: "claude-code-hook"}')" \
    > /dev/null || true
fi

exit 0

This reads the last 60 lines of the transcript, keeps only the assistant's text messages, trims it to a reasonable size, and posts it to open-context's POST /api/contexts endpoint — the same endpoint the web UI and the MCP server's save_context tool both write through. It runs with the REST API server (npm run server) already up on port 3000; no MCP connection is required for the save itself.

Registering it in settings.json

{
  "hooks": {
    "PreCompact": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/save-context.sh",
            "timeout": 15
          }
        ]
      }
    ]
  }
}

${CLAUDE_PROJECT_DIR} resolves to the project root so the same config works from any checkout. Add the identical hook under SessionEnd to get one more save whenever a session closes cleanly, catching context that never triggered a compaction at all.

Hooks vs. asking Claude to call save_context

These are complementary, not competing. Ask Claude directly — "remember that we're pinning Node to 25 for this repo" — and it calls save_context with a clean, deliberately-written summary and specific tags. That's the better save whenever you think to make it. A hook is for everything else: the session that ends without anyone saying "remember this," the long debugging thread where the useful conclusion only showed up in the last few messages before compaction. One is precise and occasional; the other is coarse and constant. Run both, and you get deliberate saves for the things you know matter, plus an automatic backstop for the things you'd only realize mattered later. For the deliberate side of that pairing, see Giving Claude Code Persistent Project Memory With MCP.

Keeping it safe and quiet

A hook script that fails shouldn't be able to break your session. Command hooks run with a timeout (15 seconds is plenty for a curl call), and for events like PreCompact and SessionEnd a non-zero exit code doesn't block anything the way it would for PreToolUse — it just means nothing got saved that round. Ending the script with || true after the curl call makes that explicit, so a stopped server or a network hiccup never surfaces as a broken Claude Code session. Nothing here leaves your machine either: the hook talks to localhost:3000, and everything it saves lands in the same local store (~/.opencontext/contexts.json by default, or whichever of open-context's 15 supported backends you've configured) that the MCP tools already use.

Give Claude Code an automatic memory backstop.

Get started with open-context →

FAQ

Do I need the MCP server running for a hook to save context, or just the REST API?

Just the REST API. The hook script calls open-context's HTTP endpoint (POST /api/contexts) directly with curl, so the npm run server process needs to be running, but Claude doesn't need to invoke any MCP tool itself for the save to happen.

Which hook event should I use — Stop or PreCompact?

Stop fires after every single turn, which is usually too frequent for a whole-conversation summary. PreCompact fires once, right before Claude Code compacts older context, which is close to the moment detail would otherwise be lost. Pair it with SessionEnd for a final save when a session closes.

Will a hook see the actual conversation content, or do I have to write my own summary?

Hooks receive a transcript_path field pointing at the full session transcript as JSONL, so a hook script can read that file and extract or summarize the parts worth keeping instead of guessing at what happened.

Does this replace the save_context MCP tool?

No. It is a safety net alongside it, not a replacement. Ask Claude to save something mid-conversation with save_context whenever you want it done deliberately, and let a hook catch what falls through the cracks automatically at session boundaries.

Is this safe if the open-context server isn't running?

Yes. A failed curl call just means nothing was saved that round, not a broken Claude Code session — events like PreCompact and SessionEnd don't block on a non-zero hook exit code the way PreToolUse can. Appending || true to the curl call makes that explicit.