I have published Caveat, a long-term memory layer for Claude Code, to npm.
What it does
When using Claude Code, you often spend more time deciphering "other people's specifications" than doing the actual implementation. You get stuck on GPU driver version constraints, failed native module builds, IDE quirks, or path issues that only occur on specific OSs. Even after you struggle and solve it once, you end up stepping into the same trap in a different project six months later. When you ask the AI, it doesn't say "I don't know" but instead acts on assumptions, causing you to waste time all over again.
Caveat is a layer where "once you jot it down, relevant notes automatically surface the moment you encounter the same situation next time." Even if you can't remember it, and even if the AI doesn't know it, the relevance is detected structurally.
Three Trigger Points
Caveat is implemented at three points using hooks.
| Trigger Point | When it runs | What it does |
|---|---|---|
| Prompt Submission | The moment a prompt is sent | Breaks down the prompt and surfaces only entries where two or more words co-occur with past notes |
| Tool Error | The moment a Claude tool call fails | Runs a background search and notifies the next turn as a known trap |
| Session Termination | When a session closes | Extracts "struggle signals" from conversation logs. Prompts the AI if there is anything that should be recorded as a new trap |
"Struggle signals" are traces where the AI might not be aware of it, but objectively it was struggling—such as tool failures, editing the same file repeatedly, repeated web searches, or re-executing Bash commands. It scans these at the end and prompts you, "You were stuck here in today's session, right? Do you want to record it as a trap?"
Design without Keyword Lists
The search logic relies solely on Co-occurrence FTS. There is no keyword correspondence table like "if the word 'rtx' comes up, display GPU-related notes."
Instead, it breaks down the input prompt and only surfaces entries where two or more words appear in the same entry simultaneously. Generic words like make or new do not trigger on their own, but when two or more technical words overlap, they match.
Even when new trap categories are added, you just add one entries/<slug>.md. You don't need to touch the code or keyword tables. The trigger expands itself.
Knowledge is markdown-in-git
The data consists of standard markdown files. SQLite is used as a derived index for searching, which can be rebuilt if deleted.
~/.caveat/own/
├── entries/
│ ├── rtx-5090-cuda-12-init-fail.md
│ ├── windows-node-spawn-cmd-enoent.md
│ └── ...
└── .git/
You can open it directly as an Obsidian vault. If you want to share it with your team, you can simply git push. There is no central server.
Public / Private Layers
Entries have a visibility attribute.
- Public: Traps anyone can encounter if they use the same external tools or specifications (GPU, build environments, IDEs, version constraints)
- Private: Project-specific context that cannot be reconstructed just by reading the code (intentional non-standard behavior, workarounds awaiting upstream fixes, custom habits)
Claude automatically determines the visibility. When in doubt, it defaults to private (to prevent leakage). If you explicitly instruct, "This should be private," that takes priority.
There is also a pre-commit hook mechanism that prevents private entries from being mixed into the shared repository.
Installation
npm install -g caveat-cli
caveat init
caveat init does the following in one go:
- Initializes
~/.caveat/ - Registers the MCP server with Claude Code
- Adds three hooks to
~/.claude/settings.json
It does not break existing hook settings (it creates a backup before merging).
caveat search "rtx" # Search existing notes
caveat serve # Start a read-only portal
caveat uninstall # Remove Claude integration only (data is kept)
No Central DB
Earlier versions had a shared database, with the concept of using caveat push to cultivate knowledge collectively. This has been abandoned.
I concluded that automatically verifying contributions from complete strangers is impossible in principle. Even if you use an LLM as a gatekeeper, it can be bypassed, and long-term latent attacks cannot be found through static analysis. Therefore, trust is built not through "automated inspection" but through "social context." I shifted to a model where you decide the scope of trust by choosing whose repositories to subscribe to.
caveat community add https://github.com/acme-corp/caveats
caveat pull
I will write about the detailed background in another article.
Requirements
- Node.js 22.5+
- Claude Code (with hooks support)
- pnpm (development only)
Status
v0.11.1, 203 tests passing. Assumes individual and small team use cases.
MIT License. Bug reports and PRs are welcome.
Top comments (0)