Caveat
Stop rediscovering the same trap. Caveat is a long-term memory layer for Claude Code and Codex: every time you bleed for an external-spec quirk or a repo-specific oddity, write it down once — and the next time anyone (you or your AI) is about to step on the same rake, the relevant note surfaces automatically.
🇯🇵 日本語版: README.ja.md
What it does in 30 seconds
npm install -g caveat-cli
caveat init # registers Claude Code MCP + hooks
caveat codex-hook install # optional: register native Codex hooks
On macOS with Homebrew Node, generated hook commands use the stable
/opt/homebrew/bin/node symlink when it resolves to the current Node binary,
instead of a versioned /opt/homebrew/Cellar/node/<version>/... path. That keeps
Claude Code and Codex hooks alive across Homebrew Node upgrades.
With Claude Code or Codex hooks enabled:
- You type a prompt →
UserPromptSubmithook surfaces matching entries via three structural gates: co-occurrence + symptom-section match + rare topical anchor. No keyword lists. Bare proper-noun mentions (RTX 5090 CUDA で何かやってる) stay silent; specific failure vocabulary plus a curated topic anchor (cudaGetDeviceCount が 0 を返す) fires the right entry. (details) - A tool returns an error → Claude hooks spawn a detached worker that searches in the background; the matching caveat lands on the next hook tick (~20ms foreground latency). Codex hooks do a bounded foreground lookup and surface the result on the next
UserPromptSubmit. Claude Code also registersPostToolUseFailurefor current failed-tool payloads. - The session ends →
Stophook parses the transcript for objective struggle signals (tool failures, repeated edits, web searches, bash retries). If any are present, it queues a compact reminder for the next hook tick so the final answer is not cluttered, then nudges the active agent to update an existing entry or record a new one on the following turn.
Claude receives Caveat reminders as <system-reminder> blocks and can use the
MCP tools to search, record, and update entries. A primary Codex session uses
Codex's native hook runtime and Codex-formatted hook output. That path calls
Caveat CLI directly; codex-sidecar remains for bounded second opinions,
review, risk-check, and isolated work.
The knowledge repo is plain markdown-in-git. Open it as an Obsidian vault. Share it as a team repo with git push. There is no central server — trust is defined socially, by who you choose to subscribe to via caveat community add <github-url>.
How it compares
| Caveat | .cursorrules / CLAUDE.md / AGENTS.md |
Cline memory-bank | RAG over docs | Notion / Obsidian (manual) | |
|---|---|---|---|---|---|
| Surfaces context automatically | ✅ 3 hook firing points | ❌ always-on, fills context | ❌ re-reads the whole bank each task | ⚠️ on explicit query | ❌ manual recall |
| Granular per-trap retrieval | ✅ FTS5 co-occurrence | ❌ monolithic file | ❌ loads the entire folder | ✅ embeddings | ❌ |
| Source of truth | markdown-in-git | a single rules file | markdown folder in workspace | vector DB | proprietary |
| Records new traps from session | ✅ via caveat_record MCP tool |
❌ | ⚠️ manual "update memory bank" command | ❌ | manual |
| Catches struggle the AI didn't self-report | ✅ transcript signal mining | ❌ | ❌ | ❌ | ❌ |
| Mixes external-spec gotchas with repo-specific context | ✅ public / private tiers | ⚠️ no separation | ⚠️ no separation | ⚠️ | ⚠️ |
Status: v0.14.10, CI green across Ubuntu/Windows and Node 22/24. Single-user and small-team workflows are the primary supported path. No central DB; no auto-subscription on install. Current handoff notes live in docs/05_next_session.md.
Earlier versions ran a central shared community DB with caveat push (fork + PR) and auto-subscribe on caveat init. That model was retired because trust over arbitrary stranger contributions cannot be reliably automated — sophisticated malicious payloads survive static gates and adversarial-gradient attacks against any LLM-based oracle. xz-utils-style long games are undetectable by static review. Trust is now defined socially (you, your team, your org). See docs/01_plan.md and the abandoned auto-merge design.
Two tiers, distinguished by third-party reproducibility:
- Public — external-spec gotchas any third party running the same tool/spec can hit (GPU drivers, native-module builds, IDE quirks, version constraints).
- Private — repo-specific non-obvious context that code reading alone cannot reconstruct (intentional non-standard behavior, workarounds awaiting upstream fixes, cross-project personal conventions).
Classification is automatic via a binary criterion in the caveat_record tool description; explicit user instruction always overrides. The pre-commit visibility gate keeps private entries out of any shared git repo. Retrieval is deliberately flat — body vocabulary naturally segregates the tiers (public bodies contain external tool names; private bodies contain repo-specific identifiers). See docs/private-tier-design.md.
Concept
flowchart LR
subgraph KB["Knowledge repo (markdown-in-git)"]
MD["entries/*.md<br/>(public + private)"]
end
MD -->|caveat index| FTS[("SQLite + FTS5<br/>trigram")]
subgraph AG["Agent session (Claude Code / Codex)"]
P["User prompt"]
T["Tool error<br/>(is_error: true)"]
S["Session end<br/>(transcript signals)"]
end
P -.->|"UserPromptSubmit<br/>事前発火"| H1{"co-occurrence<br/>+ symptom<br/>+ topical anchor"}
T -.->|"PostToolUse<br/>実行中発火 ~20ms"| H2{"async detached<br/>worker"}
S -.->|"Stop<br/>事後発火"| H3{"signal-gated<br/>+ FTS"}
H1 --> FTS
H2 --> FTS
H3 --> FTS
FTS ==>|matched entries| R["Claude: <system-reminder><br/>Codex: hook output"]
R ==> AG
markdown-in-gitis the source of truth. SQLite (FTS5 trigram) is a rebuildable derived index, gitignored.- Two sharing boundaries, enforced by the tool. Your
~/.caveat/own/is yours.caveat syncmirrors it (public + private) to a private remote for your machines/org — refusing any anonymously-readable remote.caveat publishmirrors only public entries to a public repo. Subscribers add a repo withcaveat community add <github-url-or-username>; updates flow viacaveat community pull. No central server; no automatic merge of strangers' entries — trust stays social. visibility: public | privatefrontmatter +.husky/pre-commitgate keeps private entries out of any repo you commit to.- Agent integrations. Claude Code gets an MCP server exposing 6 tools (
caveat_search/caveat_get/caveat_record/caveat_update/caveat_list_recent/caveat_pull) plus hooks. Codex gets native hooks throughcaveat codex-hook install. Both surfaces reuse the same retrieval gates — no hardcoded keyword lists:- UserPromptSubmit (事前発火): when you submit a prompt, tokenize it (path-stripping + self-identity + pure-hiragana glue removal + CJK group dedup), FTS the DB, and surface entries that pass three structural gates — (1) ≥ 2 distinct group matches (co-occurrence), (2) ≥ 1 match in the entry's
## Symptomsection (failure-state evidence), (3) ≥ 1 corpus-rarest prompt token intopical_text(title + tags + environment values, topic evidence). Bare proper-noun mentions likeRTX 5090 CUDA で何かやってるstay silent; only specific failure-state vocabulary plus a curated topic anchor (cudaGetDeviceCount,SQLITE_READONLY, …) fires the gate. No hardcoded word lists. - PostToolUse (+ Claude PostToolUseFailure) (実行中発火): when a tool returns
is_error: trueor Claude Code emits a failed-toolerrorpayload, Claude spawns a detached worker so the foreground hook returns in ~20ms. Codex performs a bounded foreground lookup because current Codex payloads and transcript timing make detached workers unreliable there. In both cases, the reminder lands on the next hook tick. In Claude-hosted sessions, an operationalcodex-sidecarcan append Codex advice after Caveat's original text. - Stop (事後発火): parse the session transcript for objective struggle signals (tool failures, repeated file edits, web searches, bash retries). If any are present, queue a compact reminder for the next context-capable hook tick and nudge
caveat_updateorcaveat_recordthere. In Claude-hosted sessions, optional Codex advice can challenge or sharpen that nudge without replacing Caveat's trigger logic.
- UserPromptSubmit (事前発火): when you submit a prompt, tokenize it (path-stripping + self-identity + pure-hiragana glue removal + CJK group dedup), FTS the DB, and surface entries that pass three structural gates — (1) ≥ 2 distinct group matches (co-occurrence), (2) ≥ 1 match in the entry's
- Codex primary hook adapter.
caveat codex-hook installregistersUserPromptSubmit,PostToolUse, andStopin~/.codex/hooks.jsonand enables[features].hooks = true. Existingcodex_hooks = trueinstalls are migrated to the canonical key. It reuses Caveat's existing search, pending-reminder, and stop-signal logic with Codex-specific payload parsing and stdout formatting. Claude hook stdout is at most one<system-reminder>block per invocation; Codex hook stdout is a single JSON object per invocation. Pending reminders are compacted before being joined into one host-specific context string. - Obsidian-compatible. The knowledge repo is a valid Obsidian vault — open it as a folder, edit with Obsidian's graph/backlinks/Dataview, the tool re-indexes on
caveat index.
Layout
packages/core/ @caveat/core — DB (node:sqlite + FTS5 trigram), indexer, frontmatter,
env fingerprint, repository, record/update, community, paths,
shared hook retrieval logic (claudeHooks.ts; Claude name
retained for the canonical Claude contract)
apps/cli/ caveat-cli (published to npm) — bundled CLI with subcommands:
init / uninstall / index [--full] / search / list / stale / show /
stats / serve / mcp-server / hook <name> / community add|pull|list /
codex-hook install|uninstall|diagnostics|... /
codex-sidecar diagnostics|smoke|run|work-smoke
apps/mcp/ @caveat/mcp — stdio MCP server exposing 6 tools via
@modelcontextprotocol/sdk. Imported by caveat-cli as `mcp-server`
apps/web/ @caveat/web — Hono SSR read-only share portal (/, /g/:id, /community) +
custom markdown-it wikilinks plugin for [[slug]] → /g/slug rendering
hooks/ pre-commit-visibility-gate.mjs (run by .husky/pre-commit) — thin
re-export wrapper around @caveat/core's findBlockedFiles
.husky/ git pre-commit wiring (husky 9)
docs/00_overview.md Documentation map and reading order
docs/01_plan.md Design source of truth (audited through Round 5, then extended
Phase 2 → 12 with implementation findings)
docs/02_audit.md Audit history (rejected proposals preserved so they don't reappear)
docs/03_dual_agent_support.md
Claude/Codex contract, sidecar policy, and smoke notes
docs/04_release_checklist.md
Required publish and post-publish verification checklist
docs/05_next_session.md Current handoff: release state, remaining smoke, closeout checks
docs/adr/ Architecture decision records
docs/archive/ Superseded drafts (legacy brainstorms, etc.)
rag/ Research asset ledger; currently only INDEX.md
Requirements
- Node 22.5+ (for
node:sqlite). Verified on Node 24.14 with bundled SQLite 3.51.2. - pnpm 10 via corepack (pinned in root
package.json'spackageManager). - git for community import (
simple-gitshells out to the system git).
Quick start (NPM user)
npm install -g caveat-cli
caveat init # one-time setup (see below)
caveat codex-hook install # optional native Codex hook setup
caveat search "rtx" # search your local entries
caveat community add https://github.com/acme-corp/caveats # subscribe to a group repo
caveat pull # git-pull subscribed repos and re-index
caveat serve # http://localhost:4242/ read-only portal
What caveat init does on first run for Claude Code:
- Writes
~/.caveatrc.json(empty{}— defaults come from a constant in the CLI) - Scaffolds
~/.caveat/own/(your knowledge repo root) +~/.caveat/index/caveat.db - Runs
claude mcp add --scope user caveat -- <node> --disable-warning=ExperimentalWarning <cliPath> mcp-server - Merges
UserPromptSubmit/PostToolUse/PostToolUseFailure/Stophook entries into~/.claude/settings.json(existing entries preserved; backup written before any change)
Use --skip-claude to skip Claude Code wiring, or --dry-run to preview. caveat uninstall reverses Claude Code changes without touching ~/.caveat/. No central DB is auto-subscribed — add knowledge sources explicitly with caveat community add.
For Codex, run caveat codex-hook diagnostics first if you want a health check.
It reports hook availability separately from whether Caveat-owned hooks are
installed.
Sharing: two boundaries, two commands
visibility is a distribution ceiling. Two commands enforce it:
-
caveat sync— sync your whole~/.caveat/own/(public and private entries) to a private git remote. This is how you keep your knowledge in step across your own machines, or share it inside an org — anyone with push/pull access to that private repo is inside your boundary. Before pushing, Caveat probes the remote and refuses to push if it is anonymously readable (a public repo), so private entries never leak by misconfiguration.caveat sync --init # gh-creates <you>/Caveat-Private (private), first push caveat sync # thereafter: commit → pull --rebase → reindex → push caveat sync --init --repo https://github.com/acme-corp/Caveat-Private.git # org / self-hostedOnce a private remote is configured, Caveat also syncs on its own — a gotcha you hit on one machine is worth little if it reaches the others two days later. A background worker runs the same cycle within a minute of recording an entry, and otherwise at most every 15 minutes when a session ends. It never blocks you, it obeys the same refuse-if-anonymously-readable rule, and repeated failures back off rather than stopping silently. Set
CAVEAT_AUTO_SYNC=offto sync only by hand. -
caveat publish— mirror onlyvisibility: publicentries to a public repo, one-way. Private entries are never written; the mirror is re-verified before every push and aborts entirely on any malformed entry.caveat publish --init # gh-creates <you>/Caveat-Public (public) caveat publish # mirror public entries, show the diff, confirm, push
Others read your public repo with caveat community add <you> (a bare GitHub username expands to <you>/Caveat-Public) then caveat pull. There is still no central server and no automatic merge of strangers' contributions — trust stays social. Contributions to someone's public repo go through its normal GitHub PR review.
Deleting and recreating a generated Caveat-Public repository can stop future
readers and crawlers from seeing the old repository, but it is not retroactive
erasure. Existing clones and copies held by GitHub caches, forks, archival
services such as Software Heritage, or event archives such as GH Archive may
remain outside the publisher's control.
The entries/ directory in this tool repository is intentionally retained as
dogfood and format examples. It is not the canonical published knowledge set;
that boundary is the generated Caveat-Public repository produced by
caveat publish.
Using an existing knowledge repo instead of ~/.caveat/own/
Write ~/.caveatrc.json:
{
"knowledgeRepo": "/absolute/path/to/your/caveats-repo"
}
(v0.2+) source_project is always written as null by caveat_record. It used to be auto-inferred from cwd via a projectRoots config field, but that leaked per-user project names into publicly-shared knowledge repos and has been removed. Set it manually in the md file if you want personal traceability on private entries.
Quick start (dev — contributing to Caveat itself)
corepack pnpm install
corepack pnpm -r build
cd apps/cli && corepack pnpm pack # caveat-cli-<ver>.tgz
npm install -g ./caveat-cli-<ver>.tgz # now `caveat` is on PATH
For npm releases, publish from apps/cli with corepack pnpm publish.
Do not use npm publish directly; pnpm normalizes workspace dev dependencies
in the packed manifest, while npm leaves workspace:* strings intact.
Release work is not done at publish time: follow
docs/04_release_checklist.md through fresh npm
install, Claude Haiku new-session smoke, Codex new-session smoke, CI, and npm
registry verification.
For iterative dev, npm link inside apps/cli/ keeps the global shim tracking your local build.
(Optional) Pre-commit gate on your knowledge repo
The tool repo already has .husky/pre-commit wired. To enable the same gate on your knowledge repo so private entries can't leak:
cd /path/to/your/caveats-repo
npm init -y # or pnpm init
npm install --save-dev husky
npx husky init
# Copy the gate script:
cp /path/to/Caveat/hooks/pre-commit-visibility-gate.mjs hooks/
# Edit .husky/pre-commit to exec that script (one line: `exec node "$(dirname "$0")/../hooks/pre-commit-visibility-gate.mjs"`)
The gate rejects any commit that stages an entries/**/*.md with visibility: private. Bypass only with git commit --no-verify (git standard), not a custom flag.
(Optional) Open knowledge repo in Obsidian
Your knowledge repo (default ~/.caveat/own/) is a valid Obsidian vault. File → Open folder as vault. Recommended plugins:
| Plugin | Purpose |
|---|---|
| Templates (core) | Settings > Templates → folder .templates/. Then Insert template inserts the frontmatter skeleton. |
| Obsidian Git | Sync your vault to GitHub from inside Obsidian. |
| Dataview | Frontmatter queries. E.g. TABLE confidence, environment.gpu FROM "entries" WHERE outcome = "impossible". |
Caveats authored in Obsidian are picked up by caveat index on next run (FTS is eventually consistent; MCP caveat_record syncs immediately).
Subscribing to other people's / team caveat repos
caveat community add https://github.com/alice/caveats-alice
caveat community pull # refresh all subscribed repos
caveat community list
caveat community remove <handle> # unsubscribe + purge db rows
caveat index # re-index to pick up new entries (or use `caveat pull` for the combined flow)
# Then search only their contributions:
caveat search "foo" --source community
URL validation is strict — only ^https://github.com/<org>/<repo>(\.git)?/?$ is accepted. GitLab / SSH / HTTP are rejected in v1.
Knowledge repo format
Each caveat is a markdown file with YAML frontmatter. Example:
---
id: rtx-5090-cuda-12-compat
title: RTX 5090 で CUDA 12.4 以前が初期化失敗する
visibility: public
confidence: reproduced # confirmed | reproduced | tentative
outcome: resolved # resolved | impossible
tags: [gpu, nvidia, cuda]
environment:
gpu: RTX 5090
cuda: ">=12.5"
source_project: llm-infer-bench
source_session: "2026-04-18T12:34:56Z/abcdef012345"
created_at: 2026-04-18
updated_at: 2026-04-18
last_verified: 2026-04-18
---
## Symptom
## Cause
## Resolution
## Evidence
See docs/01_plan.md for the full schema, semver matching rules, and MCP tool specs.
Development
corepack pnpm -r test # 259 tests across 5 packages
corepack pnpm -r typecheck
corepack pnpm -r build
Per-package:
corepack pnpm --filter @caveat/core test
corepack pnpm --filter caveat-cli test
corepack pnpm --filter @caveat/mcp test
corepack pnpm --filter @caveat/web test
corepack pnpm --filter @caveat/hooks test
Contributing: see CONTRIBUTING.md.
License
MIT
No comments yet
Be the first to share your take.