Browse docs

Hooks Reference — Every Hook, Every Category

Purpose: Canonical catalog of every hook coding-os ships. Lists what it fires on, what it blocks vs warns, and where its source lives. This is the file to read when cos hooks-log shows a hook you don't recognize, or when deciding whether to add a new hook vs extend an existing one.

Read when: onboarding to hook authoring · deciding which hook governs a specific behavior · debugging why a hook fired or didn't.

Nav: Section Index | Docs Index

SSOT for registration: src/core/hooks/registry.yaml. Adapter template files are GENERATED from it by make regen-adapter-templates. Never hand-edit src/adapters/*/settings.template.json or src/adapters/*/hooks.template.json — the golden-parity tests (test_adapter_parity) fail if they drift from the registry.

Effect classes — what the agent sees

Every hook falls into one of three classes by effect:

Class Exit Agent visibility Example use
BLOCK exit 2 + stderr appears in agent context; tool call is refused enforce pre-conditions, reject anti-patterns
WARN exit 0 + stderr advisory stderr in agent context; action proceeds nudge toward best practice
SILENT exit 0 (log-only) invisible to agent; only .coding-os/.hooks.log records passive observation

Per-line log format

Every hook calls cos_log_hook from src/core/hooks/cos-env.sh which writes lines of the form:

[ISO-8601] [hook-name] [action] agent=X session=Y task=Z detail...

The agent=X session=Y task=Z triplet (added 2026-04-18) lets downstream tools separate activity across concurrent chats or runtimes:

cos hooks-log --agent claude                 # only claude
cos hooks-log --session ses-20260418-...     # one specific chat
cos hooks-log --task governance-xyz          # one specific task across sessions
cos hooks-log --hook enforce- --follow       # live stream of enforcement hooks

Catalog

Safety (7 hooks) — PreToolUse BLOCK

Hook Fires on What it blocks
block-secrets Bash, Write/Edit AWS keys, tokens, .env leaks in commands or file content
block-dangerous-commands Bash recursive rm of critical paths (root/cwd/parent/glob/$HOME, project dirs, any top-level absolute dir incl. trailing-slash /etc/ — via _helpers/check_dangerous_rm.py), git push --force / +main refspec to main/master, git reset --hard, SQL DROP TABLE/DROP DATABASE
block-uv-heredoc Bash uv run ... <<EOF pattern (Rule 9 — silently hangs)
block-bad-patterns Write/Edit bare except: pass, mock-where-real-needed, known anti-patterns
block-protected-files Write/Edit edits to CLAUDE.md, AGENTS.md, .coding-os/, src/core/rules/, src/core/hooks/ unless active task name contains governance / docs-update (Rule 8)
block-migration-conflict Write/Edit duplicate migration version numbers in src/core/thinking_os/database.py (Rule 10 — append-only)
block-hardcoded-literals Write/Edit "django" / "claude" / "python-django" as quoted literals in src/cli/*.py (Rule 12 — data-driven only)

Enforcement (8 hooks) — PreToolUse BLOCK

Hook Fires on Blocks until
enforce-skill Write/Edit on .py/.ts/.tsx domain skill invoked (clean-code, python-django, nextjs-react, …)
enforce-zoom Write/Edit after Complexity Gate = COMPLICATED/COMPLEX $COS_PANEL_DIR/.zoom-checkpoint records PROBLEM_FRAMED (per-panel)
thinking_os-gate Write/Edit $COS_PANEL_DIR/.thinking_os-gate records <CYNEFIN> <DIM> classification (per-panel)
enforce-task-start Write/Edit $COS_PANEL_DIR/.task-current points at an active task (or CLEAR-1 fast-path) — per-panel
enforce-doc-anchor Write/Edit on code $COS_PANEL_DIR/.doc-anchor points at a real doc (Rule 0 — docs-first), per-panel
enforce-memory-check Write/Edit $COS_PANEL_DIR/.memory-check records a recent cos_search for past patterns, per-panel
enforce-template Write on specific markdown paths proper template bootstrap ran (see template-enforcement.md)
enforce-verify cos task-done Verification Matrix commands passed for the changed domain
test-governor PreToolUse Bash (pytest invocations) no duplicate suite run on the same tree (commit-keyed ledger), one heavy run per machine (.test-run.lock), full sweeps need COS_FULL_SWEEP_OK=1 + reason — spec: test-governance.md
record-verify-auto PostToolUse Bash (suite commands) auto-records PASS/FAIL + {git_head, dirty_digest, agent} to .last-verify.json; clears the run lock

Observability (6 hooks) — SILENT log-only

Hook Event What it records
capture-observation PostToolUse Write/Edit one row in observations (synchronous since TASK-048 — a bg spawn was reaped before commit; resolves capture.py via the symlink; errors → .coding-os/.capture-errors.log)
verify-changed-file PostToolUse quick sanity (syntax, refs) on just-edited file
track-skill PostToolUse which skill was active when a tool fired
session-context SessionStart + UserPromptSubmit on startup: orphan-recover previous session, clear stale state, new session-id. On compact/resume: re-inject workflow rules. On UserPromptSubmit (Codex): lightweight context refresh only — no new session-id, no state reset
session-end Stop builds session_summaries row + runs session_enrich
check-capture-worked Stop reads .capture-errors.log; surfaces silent capture failures

Reminder (5 hooks) — PostToolUse WARN

Hook Fires on Reminds about
regen-reminder Edit on src/templates/*/stack.yaml, src/adapters/*/adapter.yaml, src/core/hooks/registry.yaml, or any scaffold/** run make regen-rules / make manifest-regen / make regen-adapter-templates
test-first-reminder Write/Edit on code without paired test suggest writing test alongside code
doc-sync-reminder Edit on src/core/hooks/*.sh or src/core/thinking_os/tools/*.py docs in docs/engineering/ / src/core/docs/ may need sync
remind-learn-validate PostToolUse at end-of-session call cos_learn_validate on suggested patterns that were used
remind-dogfood Edit on src/core/** remember this repo dogfoods itself; run make dogfood after core edits

Cognition (3 hooks) — UserPromptSubmit nudges

Surface the right tool / discipline at prompt time so the agent never reaches a downstream BLOCK uninformed. Each is per-session debounced via a marker file under $COS_PANEL_DIR/ (panel-private — two Claude tabs nudge independently).

Hook Fires on Surfaces
nudge-thinking-os UserPromptSubmit (heuristic) Complexity Gate + Rule 18 task reconciliation for COMPLICATED+ prompts. Debounce: .zoom-prompt-suggested
nudge-graph-os UserPromptSubmit (13 structural patterns, bilingual EN+FA) Recommends specific cos_graph_* tool BEFORE Read/grep. Debounce: per-pattern marker under .graph-nudge/
nudge-docs-first UserPromptSubmit (code-edit intent, bilingual EN+FA) — silent when .doc-anchor already populated or task-mode in query/chore/adhoc Recommends cos_doc_search / cos_doc_header + points at docs-first-protocol.md before any code Write/Edit (Rule 0 + 19). Debounce: .docs-first-nudged

Codex receives the same hooks via the codex-userpromptsubmit-dispatch.sh dispatcher (concurrent UserPromptSubmit triggers coalesced through src/adapters/codex/adapter.yaml::hook_dispatchers).

Meta (3 hooks) — BLOCK or WARN on governance docs

Hook Event Purpose
check-agents-md-size PostToolUse after AGENTS.md edit warn if size > 30 KB (context budget)
check-agents-md-refs PostToolUse after AGENTS.md edit warn if referenced files/paths are missing
warn-mcp-down SessionStart WARN if thinking_os MCP can't be reached (session is cognitively blind)

JIT rule reminders (jit-recall + jit-rules.tsv)

jit-recall (PreToolUse Write|Edit, warn-only, fail-open) surfaces two kinds of just-in-time context right before an edit:

  1. Past friction lesson for the target file (from learned_patterns, debounced once per file per session).
  2. Convention-rule one-liner from src/core/hooks/jit-rules.tsv — tab-separated glob<TAB>rule_id<TAB>message lines matched against the edited path, normalized to a leading / so relative (Codex) paths match too (# comments allowed). Each rule surfaces at most once per session: markers live in $COS_PANEL_DIR/.jit-nudge/, which session-context.sh prunes wholesale at SessionStart like the other *-nudge/ dirs. The hook locates the tsv through _cos_helpers_dir (cos-env.sh) because installers symlink only *.sh — a sibling data file is never at $(dirname "$0") in a consumer project.

Why: convention-only rules (no blocking hook — e.g. api-contract-discipline) are injected at session start and forgotten by edit time; re-injecting the one-liner at the moment the matching file is opened for writing puts the rule at peak attention for near-zero token cost. Add a mapping line instead of a new nudge hook whenever the trigger is "editing a file that matches a glob" — a new hook for that is parasitic duplication. Rules that already BLOCK must not be duplicated here.

Adding a new hook

  1. Write the script: src/core/hooks/<new-name>.sh. Source cos-env.sh and call cos_log_hook <new-name> <action> <detail>.
  2. Register in src/core/hooks/registry.yaml with fields: id, script, description, category, phase, events[].
  3. make regen-adapter-templates — regenerates both adapter JSON files.
  4. make dogfood — re-installs adapters in this repo (symlinks are already live, but settings JSON needs refresh).
  5. make verify-hooks — syntax-check all hooks.
  6. Write a test for expected BLOCK/WARN/SILENT behavior in src/core/thinking_os/tests/ or tests/.

Debugging — "why didn't my hook fire?"

Zero entries in .coding-os/.hooks.log for a hook you expected means the agent runtime isn't delivering the event. Checklist:

  1. cos hooks-list --agent claude (or codex) — is the hook registered for that runtime?
  2. Open .claude/settings.json (or .codex/hooks.json) — does the hook appear under the right matcher?
  3. If you edited registry.yaml mid-session and forgot make regen-adapter-templates, templates drift — the golden-parity test (test_adapter_parity) catches that in CI.
  4. Runtime reload — Claude Code/Codex may need a restart to pick up new settings.

Why log-only observability hooks don't surface in agent context

By design. Silent hooks are for telemetry, not control. Surfacing every capture-observation fire to the agent would flood context and crowd out the actual work. Use cos hooks-log --follow in a second terminal when you need to watch live.

References