by qkal
Stops AI coding agents from claiming work is done without evidence. Deterministic hooks decide, TypeSafe's Jev advises. Append-only ledger, zero runtime dependencies.
# Add to your Claude Code skills
git clone https://github.com/qkal/CannySee how Canny compares with popular alternatives.
Canny is an open-source ai agents skill for AI coding assistants such as Claude Code, Codex CLI, and ChatGPT, built by qkal. Stops AI coding agents from claiming work is done without evidence. Deterministic hooks decide, TypeSafe's Jev advises. Append-only ledger, zero runtime dependencies. It has 50 GitHub stars.
Canny's catalog security scan is still queued. You can run an instant dependency and prompt-injection check now with the "Scan for vulnerabilities" button above.
Clone the repository with "git clone https://github.com/qkal/Canny" and add it to your Claude Code skills directory (see the Installation section above).
Canny is primarily written in TypeScript. It is open-source under qkal on GitHub, so you can review or fork the full source.
Yes. SkillsLLM lists many other AI Agents skills you can browse and compare side by side. Open the AI Agents category from the badge at the top of this page, or use the Related Skills and comparison links further down to weigh Canny against similar tools.
No comments yet. Be the first to share your thoughts!
⚠️ Third-Party Software Notice
This skill is third-party open-source software developed and hosted independently on GitHub. SkillsLLM is an informational directory and does not control or maintain the underlying repository.
Any security checks, ratings, or warnings displayed by SkillsLLM are automated and limited in scope. They do not constitute a security certification or guarantee that the software is safe, error-free, or free from malicious code, vulnerabilities, compromised dependencies, or prompt-injection risks.
Review the source code, permissions, dependencies, and configuration before installing or running any third-party skill. Use is at your own risk. To the maximum extent permitted by applicable law, SkillsLLM is not liable for losses arising from third-party software.
The deep catalog scan for this skill is still queued. Run an instant dependency check now instead.
A supervision layer for AI coding agents. It hooks into Claude Code and Codex CLI, keeps a ledger of what the agent actually did, and will not let it finish on a claim.
Install · What it looks like · What blocks · Configuration · Commands · Does it help? · Contributing
Done. Skipped tests — one-liner, no branch to break.
That is Claude Code, verbatim, during this project's first live run. It had been asked to add a function, it wrote the file with a shell heredoc, ran nothing, and finished. No error. No warning. Nothing in CLAUDE.md could have stopped it, because a rules file only asks the model to remember, and nothing checks that it did.
Canny is the hook that noticed. On the next run of the same prompt, the agent's "done" was refused with this message:
Canny: math.js changed, but no check has passed since the last edit. The last command was
cat > math.js <<'EOF' …(exit 0). Run the project's checks and fix what fails before finishing. A test, build, lint, or type-check command counts. If no check applies to this change, say so explicitly and stop again.
Claude ran npm test. It passed. The next "done" went through. The whole exchange is in the session ledger below.
Facts go to code. Judgments go to Jev. Only facts can block.
A fact is something the ledger can prove: a file changed, a command ran, it exited 1, the same command failed with the same output three times, the text about to be written contains an AWS key. Code decides those, offline, with no API key.
A judgment is something code cannot decide: does this message claim the work is done, does this diff break the rule "never hardcode model IDs". Those go to Jev, TypeSafe's decision model, which answers typed yes/no questions with a calibrated probability in about a quarter of a second.
Jev never blocks. A "done" claim is refused because the ledger holds no passing check, not because a probability crossed a line. A rule violation becomes a note in the agent's context, not a wall. When Jev is unsure, or there is no key, the deterministic rule stands alone.
The same session always produces the same verdict, and canny replay proves it from the ledger.
Canny is not on npm. Your agent installs it from this repository: a clone and one command. The compiled CLI is committed, so there is nothing to build. You need git and Node 22 or newer.
Paste this into Claude Code or Codex, inside the project you want guarded:
Install Canny (https://github.com/qkal/canny), a supervisor that checks your work through this agent's hooks, and hook it into this project.
1. Confirm `node --version` is 22 or newer. If not, stop and tell me.
2. If ~/.canny/src exists, run `git -C ~/.canny/src pull --ff-only`. Otherwise run `git clone https://github.com/qkal/canny.git ~/.canny/src`. There is nothing to build or install.
3. From the root of this project, run `node ~/.canny/src/dist/cli.js init` and show me the hook entries it wrote.
4. Run `echo canny-check`, then `node ~/.canny/src/dist/cli.js status`. If status lists a session with at least one event, the hooks are live. If it says no sessions were recorded: on Claude Code ask me to restart you; on Codex remind me to run /hooks to trust the new hooks.
5. Change nothing else. Tell me what you did in five lines or fewer.
init writes hook entries for the agents it finds installed, so the same prompt works in either agent. To choose explicitly, change step 3 to init --claude or init --codex. To guard every project instead of this one, use init --global, which writes to ~/.claude/settings.json and ~/.codex/hooks.json.
To update later:
Update Canny: run `git -C ~/.canny/src pull --ff-only` and tell me what changed, from its CHANGELOG.md, since the previous commit.
To remove it from a project:
Remove Canny from this project: from the project root run `node ~/.canny/src/dist/cli.js remove` and show me what it took out. Leave ~/.canny alone.
By hand, the same thing is two commands, and a third if you want a canny on your PATH:
git clone https://github.com/qkal/canny.git ~/.canny/src
node ~/.canny/src/dist/cli.js init
ln -s ~/.canny/src/dist/cli.js ~/.local/bin/canny
With canny on your PATH, init writes canny hook into the hook config instead of the absolute path.
Every hook event lands in an append-only ledger, one file per session under ~/.canny/sessions/. This is the live run from the top of the page, as canny status and the ledger show it. Only paths and outcomes are stored, never file contents.
event what happened exit verdict
Bash ls -a && cat package.json 0
Bash cat math.test.js 0
Bash cat > math.js <<'EOF' … EOF 0 edit recorded: math.js
Stop "…Done." — block: math.js changed, no check has passed
Bash node --test 2>&1 | tail -20 0 not on the list of checks that day
Stop "…Done." — block: still no passing check
Bash npm test 0 a check
Stop "npm test passes: 1 test, 0 failures … Done." — allow
node --test was not yet on the list of commands that count, so Claude was blocked a second time and reached for npm test. The list is configurable, and that one is on it now.
Run canny replay on any session and it re-derives every Stop verdict from the recorded facts and the recorded Jev answers, then reports any mismatch. There are none.
flowchart LR
A[PreToolUse] --> B[Pattern checks]
B -->|deny / ask| X((tool call))
C[PostToolUse] --> D[Evidence ledger]
C --> E[Jev: does this edit break rule X?]
E -->|note| Y((agent context))
F[Stop] --> G[Ledger: check passed since last edit?]
G -->|no| H[Jev: does the message claim done?]
H -->|not a claim| I[allow]
H -->|claim or unsure| J[block]
G -->|yes| I
Claude Code also gets PostToolUseFailure, because that is where it reports a command's non-zero exit. Only the pattern checks and the ledger gate can block. Everything Jev says becomes a note.
A fourth hook, SessionStart, tells the agent up front what the done-gate asks for and names the project's test command when package.json, a justfile, a Makefile, Cargo.toml, or go.mod gives one. An agent that knows the rule before its first Stop does not pay a blocked Stop and an extra turn to learn it. Projects set up before this hook existed get it from running canny init again.
| Check | When | Decided by | Outcome |
|---|---|---|---|
| A code file changed and no test, build, lint, or type-check command has passed since | Stop | ledger | block |
Content about to be written contains a secret shape: AWS, GitHub, Slack, Stripe, Google, OpenAI or Anthropic keys, private key blocks, or password = "…" with real-looking entropy. Shell commands that write a file are read the same way. A .env file that git ignores is exempt |
PreToolUse | pattern | deny |
An edit removes test cases, adds .skip, .only, xit, @pytest.mark.skip, t.Skip, #[ignore], @Disabled, XCTSkip and friends, or deletes a test fil |