{"id":51117641,"url":"https://github.com/nadimtuhin/claude-code-self-critic","last_synced_at":"2026-06-24T23:30:20.752Z","repository":{"id":366133435,"uuid":"1275123586","full_name":"nadimtuhin/claude-code-self-critic","owner":"nadimtuhin","description":"Self-critique and stuck-detection hooks for Claude Code agents. Deterministic fact-gating catches hallucinated claims before a turn completes.","archived":false,"fork":false,"pushed_at":"2026-06-20T11:03:03.000Z","size":41,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-20T13:05:09.649Z","etag":null,"topics":["agent-safety","agentic-coding","ai-agent","claude","claude-code","claude-hooks","guardrails","hooks","llm","self-critique"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nadimtuhin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-20T09:27:31.000Z","updated_at":"2026-06-20T11:03:07.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/nadimtuhin/claude-code-self-critic","commit_stats":null,"previous_names":["nadimtuhin/claude-code-self-critic"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/nadimtuhin/claude-code-self-critic","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nadimtuhin%2Fclaude-code-self-critic","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nadimtuhin%2Fclaude-code-self-critic/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nadimtuhin%2Fclaude-code-self-critic/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nadimtuhin%2Fclaude-code-self-critic/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nadimtuhin","download_url":"https://codeload.github.com/nadimtuhin/claude-code-self-critic/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nadimtuhin%2Fclaude-code-self-critic/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34753781,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-24T02:00:07.484Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agent-safety","agentic-coding","ai-agent","claude","claude-code","claude-hooks","guardrails","hooks","llm","self-critique"],"created_at":"2026-06-24T23:30:19.730Z","updated_at":"2026-06-24T23:30:20.743Z","avatar_url":"https://github.com/nadimtuhin.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Self-Critic Hooks for Claude Code\n\n**Catch your AI agent lying to you — before the turn ends.**\n\nTwo zero-dependency hooks that give Claude Code real-time self-critique and stuck-detection. Deterministic fact-gating extracts evidence, applies rule-based checks, and only escalates to a cheap LLM call to suppress false positives. The LLM never authors the critique — it only votes.\n\n---\n\n## The Problem\n\nYou've seen it. Your agent says \"all tests pass\" — but you check, and they don't. It claims \"the build succeeds\" — but there's a compile error. It repeats the same failing command 5 times in a row, convinced this time will be different.\n\nAgents hallucinate success. They get stuck in loops. And there's no safety net.\n\n## The Solution\n\nTwo hooks that run at the right moments:\n\n**Stop Hook (Self-Critic)** — Runs at the end of each turn. Extracts evidence from the transcript (did it actually run tests? did it read the file it claims verified?). Applies deterministic rule-based gates. If claims are unbacked by evidence, it blocks the turn and tells the agent exactly what's missing. Escalates to a Haiku veto only to suppress false positives — Haiku never writes the critique.\n\n**PreToolUse Stuck-Detector** — Runs before every Bash, Edit, or Write. Detects when the same flag-normalized command or file target is repeated 3+ times. Sends a mid-turn nudge to course-correct before the turn spirals. Window resets each turn.\n\n---\n\n## Quick Start\n\n**One-liner (no clone needed):**\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/nadimtuhin/claude-code-self-critic/main/install.sh | bash\n```\n\n**Or clone and run:**\n\n```bash\ngit clone https://github.com/nadimtuhin/claude-code-self-critic\ncd claude-code-self-critic\n./install.sh\n```\n\nThat's it. The installer runs the full test suite in `/tmp` first. If tests pass, it deploys hooks and auto-merges them into `~/.claude/settings.json`. If tests fail, it aborts without touching anything.\n\nRequires `node \u003e=20` and `jq`.\n\n```bash\n./install.sh              # test in /tmp, then install/update\n./install.sh --test-only  # just run tests, don't deploy\n./install.sh --uninstall  # remove hooks cleanly\n```\n\n---\n\n## Why This Approach\n\n| Approach | Problem |\n|----------|---------|\n| Trust the agent | Agents hallucinate success claims |\n| Pure LLM critic | Expensive, slow, unreliable, writes its own opinions |\n| Pure rules | Rigid, can't suppress false positives |\n| **This: Deterministic-first + LLM veto** | **Cheap, reliable, LLM only votes to suppress** |\n\nThe key insight: don't let the LLM write the critique. Extract evidence deterministically, apply rules, build a block reason from unbacked claims. Then — and only then — let a cheap Haiku call vote \"is this a false positive?\" The LLM is a tiebreaker, not the author.\n\n---\n\n## Architecture\n\n```\nsrc/\n  fact-gate.mjs     # Evidence extraction + rule-based gates (pure, testable)\n  critic-core.mjs   # Fact-gate decision logic + episode tracking (pure)\n  stuck-core.mjs    # Repeat detection + escalation logic (pure)\n  evidence.mjs      # Test-run detection, command parsing (I/O edge)\n  state.mjs         # File-based state persistence (I/O edge)\n  stop-hook.mjs     # Stop hook entry point (thin wiring)\n  pretool-hook.mjs  # PreToolUse hook entry point (thin wiring)\ntest/\n  *.test.mjs        # 40 tests, zero dependencies\n  fixtures/         # Real transcript fixtures\n```\n\nPure cores have no I/O — 100% testable. I/O lives at the edges. Thin hooks wire them together. This is a deliberate separation: the decision logic is deterministic and fast; the LLM is isolated to a single veto call with a hard timeout.\n\n## Key Design Decisions\n\n- **Deterministic fact-gating first** — Extract evidence (ran tests? read files?) before any LLM call\n- **Haiku veto, not Haiku critic** — LLM only suppresses false positives, never authors the critique\n- **Episode cap of 1** — Prevents infinite critique loops (critique-the-critique-the-critique)\n- **Fail-open everywhere** — Any hook error allows the turn. A bug degrades to \"un-critiqued,\" never a wrongful block\n- **Flag-normalized Bash matching** — `npm test -- --grep foo` and `npm test --grep foo` are the same target\n\n## Hardcoded Defaults\n\nNo config file. Tuned defaults baked in:\n- `model=haiku` — Fast, cheap, good for veto\n- `MAX_ROUNDS=1` — Episode cap (prevent infinite loops)\n- `STUCK_THRESHOLD=3` — Nudge after 3 repeats\n- `MIN_CRITIQUE_CHARS=80` — Turns shorter than this are trivial, skipped\n- `CRITIC_TIMEOUT=60s` — Haiku veto wall-clock timeout\n\n## Test\n\n```bash\nnode --test\n```\n\n40 tests across fact-gate, evidence, critic-core, stuck-core, and state. Zero dependencies.\n\n## Fail-Open Guarantee\n\nAny hook error (file I/O, JSON parse, LLM timeout) allows the turn to proceed. Hooks never block due to their own bug — only due to detected agent issues.\n\n## Known Limitations\n\n- **Per-turn latency:** A turn that trips the fact-gate incurs one synchronous ~7-10s Haiku call. Only tripped turns pay this; clean turns are free.\n- **Stuck-detector is coarse:** Matches flag-normalized Bash target or exact file_path. Editing the same file 3x in one turn can produce a spurious nudge (non-blocking, window resets each turn).\n- **Transcript boundary:** Evidence scanning scopes to the current turn. On transcripts with no string-content user prompt (some sub-agent/resume shapes), it conservatively reports `ranTests=false`.\n\n## License\n\nMIT © Nadim Tuhin. See [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnadimtuhin%2Fclaude-code-self-critic","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnadimtuhin%2Fclaude-code-self-critic","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnadimtuhin%2Fclaude-code-self-critic/lists"}