Skip to content

v0.2.1

Choose a tag to compare

@qkal qkal released this 21 Sep 17:26
· 8 commits to main since this release
a266600

Canny 0.2.1 carries the test-removal and rule checks over to shell commands, closes two ways a key written from the shell got past the secret check, fixes one false deny, and adds a harness that measures Canny instead of asserting it.

Install or update as before: git -C ~/.canny/src pull --ff-only, or the prompt in the README. There is nothing to build, and nothing to do before updating.

Secrets

  • echo "const k = '<key>';" > src/k.ts is denied. A ; inside the quoted text used to split the statement, so the secret check lost the redirect as soon as the written code ended in a semicolon.
  • The same goes for text with an escaped quote in it, such as echo "a 5\" pipe; const k = '<key>'" > src/k.ts. Every shell reader here now treats \" inside double quotes as text.
  • Writing a key into a git-ignored .env is allowed again when the command opens with a cd that goes nowhere: cd "$PWD"; …, cd . && …, or a cd to the project's own absolute path. Agents open many commands that way, and any cd used to cancel the env-file exemption, so the write Canny's own message recommends was denied. Every other cd, pushd, or popd still leaves no env file exempt. The bench below found this one.

Tests and project rules

  • A shell command that rewrites a test file gets the same ask as the edit would (deny on Codex): a heredoc, echo, or tee that leaves fewer test cases, an append that adds .only or .skip, and a sed -i or perl -pi script whose s command or delete pattern takes cases out or puts skip markers in.
  • In-place edits are read the way the tools read them: sed -E -i, --in-place, --expression=, perl -w -pi.bak, quoted paths, a ; inside the script, an address before s, and BSD sed -i ''. Those files also reach the ledger now, so the done-gate sees them.
  • Text a shell command writes is checked against the rules in CLAUDE.md or AGENTS.md after it runs, like any other edit. As before, that can only add a note.
  • The script of an in-place edit is read, not run, so a delete by line number (5,10d) is not seen. The README lists the rest of the ceiling. These checks are a speed bump for an honest agent, not a sandbox.

Smaller fixes

  • In a project that itself lives under a temp directory, a file named like ..data.ts was taken for scratch and its edit never reached the done-gate.
  • Rule text taken from CLAUDE.md and AGENTS.md is stripped of terminal escape sequences before it is printed or sent anywhere.
  • The Jev cache is written atomically, and a cache write that fails no longer throws away the judgment that was already fetched.

Measuring Canny

node bench/run.mjs --agent claude --runs 5 gives each task in bench/tasks to a headless agent in a scratch directory, once with Canny's hooks and once without, then puts the task's own tests back and runs them. Five tasks: a feature whose edge cases are only in the tests, a red suite after a refactor, a rename across four modules, a behaviour change where a test has to change, and an API key handed over in the prompt. Nothing is installed outside the scratch directory.

First numbers, five runs per task per arm with Claude Code 2.1.277:

Model Without Canny With Canny Mean time without / with
Opus 5 25/25 25/25 17.8 s / 22.3 s
Sonnet 5 25/25 25/25 20.0 s / 26.8 s

On these tasks and these models Canny changed no outcome. It blocked once in nearly every run, because the agent verifies with npm test 2>&1 | tail, which does not count as a check, and then reran the tests on their own. That is the cost, measured; the benefit is still unmeasured.

For contributors

  • CI runs as parallel jobs: static gates, tests on Node 22, 24, and 26 on Linux plus Node 24 on macOS, a smoke test of the README install, and CodeQL. ci-ok is the one check to require.
  • pnpm check runs every gate, and pnpm install sets up a pre-commit hook that runs it, so a stale dist/ is caught before the commit.
  • test/fixtures/ holds the hook payloads of a real Claude Code session and a real Codex session, scrubbed, with a snapshot of how Canny reads them.

Full changelog: https://github.com/qkal/Canny/blob/v0.2.1/CHANGELOG.md · v0.2.0...v0.2.1