Repository navigation
Releases: datacrate360/agent-countercheck
Release list
Agent Countercheck v0.5.3
- Fix: on macOS and Linux, processes a check leaves running in the background are stopped when the check ends.
A check that started a server orsleep 300 &and exited could pass while that process kept running (and kept
its port open) afterverifyfinished, beyond the check's timeout. Each check already ran in its own process
group, but the group was only killed on timeout; now it is killed after every check, as Windows already did with
its Job Object. Reported by Perch Scan. A process that deliberately leaves the group (setsid) can still escape
on Unix; the macOS sandbox and a container remain the way to contain a hostile check.
Install
| You use | Command |
|---|---|
| Python | pip install -U agent-countercheck · uvx --from agent-countercheck countercheck verify |
| macOS / Linux, Homebrew | brew trust --formula datacrate360/tap/agent-countercheck && brew upgrade agent-countercheck |
| macOS / Linux, any shell | curl -fsSL https://raw.githubusercontent.com/datacrate360/agent-countercheck/PROD/install.sh | sh |
| Windows 10/11, PowerShell | irm https://raw.githubusercontent.com/datacrate360/agent-countercheck/PROD/install.ps1 | iex |
Agent Countercheck v0.5.2
- Fix: protected files stored in Git LFS work again (#11).
Since v0.5.1 the base is read from git's own objects, which for an LFS file is the small pointer, so an untouched
LFS test fixture counted as changed and the checks ran against the pointer. Now the real content comes from the
local LFS store, checked against the pointer's SHA-256 and size: an untouched file isn't a change, an edited one is
put back with its real content, and a changed LFS store is refused like a changed git object. If the content isn't
on the computer,verifysays so (git lfs pull) instead of failing the check. Golden expected files stored in LFS
are read the same way.
Install
| You use | Command |
|---|---|
| Python | pip install -U agent-countercheck · uvx --from agent-countercheck countercheck verify |
| macOS / Linux, Homebrew | brew trust --formula datacrate360/tap/agent-countercheck && brew upgrade agent-countercheck |
| macOS / Linux, any shell | curl -fsSL https://raw.githubusercontent.com/datacrate360/agent-countercheck/PROD/install.sh | sh |
| Windows 10/11, PowerShell | irm https://raw.githubusercontent.com/datacrate360/agent-countercheck/PROD/install.ps1 | iex |
Agent Countercheck v0.5.1
Upgrade: security fix. With v0.5.0 and earlier, an agent that changed settings in the project's own .git could
get VERIFIED for broken code with weakened tests. Found in an audit (docs/audit/2026-10-findings.md).
- Fix: what changed is decided from the files themselves, and the base from checked git objects. Countercheck
used git's index andgit diffto find changed files, and trusted git to show the base revision. Settings in the
worker's own.gitcould make an edited protected file look unchanged, or make the base show other content, and
turn a FAILED into VERIFIED. Now the clean copy's bytes are compared with the base, and every git object read for
the base is hashed again and must match its id (replace refs are ignored); a mismatch stops the run with an error.
A protected file that differs only by CRLF line endings is not reported as changed and still runs with its base
bytes. Protected files stored through a git filter (e.g. Git LFS) are compared as stored in git. - Fix: nothing is written or deleted through a symlinked folder in the clean copy. If the worker replaced a
folder with a symlink out of the project, putting back its protected files wrote them through the link, and a
golden check's old output file was deleted through it. Now such a link is replaced by a real folder in the copy
before protected files are restored, a golden output behind it is left alone (and refused after the run), and a
linkfolder behind it is not linked. - Docs fix: the README's "at least N tests" example could pass failing tests. Its exit code came from
grep -q '^OK'on the output, so code that printedOKand a few... oklines passed while its tests failed. The
example now keeps unittest's own exit code and counts tests from itsRan N testsline.
Install
| You use | Command |
|---|---|
| Python | pip install -U agent-countercheck · uvx --from agent-countercheck countercheck verify |
| macOS / Linux, Homebrew | brew trust --formula datacrate360/tap/agent-countercheck && brew upgrade agent-countercheck |
| macOS / Linux, any shell | curl -fsSL https://raw.githubusercontent.com/datacrate360/agent-countercheck/PROD/install.sh | sh |
| Windows 10/11, PowerShell | irm https://raw.githubusercontent.com/datacrate360/agent-countercheck/PROD/install.ps1 | iex |
Agent Countercheck v0.5.0
- Receipts can say which changed files no check covers (#6).
Addcovers = ["backend/**"]to a[[check]];[[forbid]]and[[coverage]]cover theirpaths. The receipt
then lists every changed file none of them covers, andshow,reportanduishow it. Top-level
uncovered = "unverified"turns such a VERIFIED into UNVERIFIED. Off until a check declarescovers, so existing
projects see no change; read from the base revision like every other rule. docs/checks.md "Which files the checks
cover".
Install
| You use | Command |
|---|---|
| Python | pip install agent-countercheck · uvx --from agent-countercheck countercheck verify |
| macOS / Linux, Homebrew | brew trust --formula datacrate360/tap/agent-countercheck && brew install datacrate360/tap/agent-countercheck |
| macOS / Linux, any shell | curl -fsSL https://raw.githubusercontent.com/datacrate360/agent-countercheck/PROD/install.sh | sh |
| Windows 10/11, PowerShell | irm https://raw.githubusercontent.com/datacrate360/agent-countercheck/PROD/install.ps1 | iex |
Every install gives you the countercheck command.
Agent Countercheck v0.4.0
Upgrading: nothing to change in your projects. Homebrew users: brew trust --formula datacrate360/tap/agent-countercheck, then brew upgrade (Homebrew 7 asks before loading a renamed third-party formula).
- Renamed to Agent Countercheck, to make clear it's for AI agents. The command is still
countercheck, and so are
the.countercheck/folder, the hooks and theCOUNTERCHECK_*settings: existing projects need no change.- GitHub:
datacrate360/agent-countercheck(the old address redirects). - PyPI:
pip install agent-countercheck/uvx --from agent-countercheck countercheck.pip install countercheck
keeps working: from this release it installsagent-countercheck. - Homebrew:
brew install datacrate360/tap/agent-countercheck; the tap renames the old formula, so
brew upgrademoves existing installs over.
- GitHub:
- New look for
countercheck uiandcountercheck report, in DataCrate360's teal and coral: a header, cards for the
totals, verdict pills, and runs that open and close. Still no web fonts, images or outside requests. - Fixed: the
countercheck uipage showed "disconnected" and stopped updating after a new receipt landed. The
server handled one connection at a time and answered a browser's spare, still-silent connection with400 Bad Requestafter 2 s; the browser then read that stale reply for its next update. Silent connections are now closed
without a reply, and each connection has its own thread (at most 32), so one can't hold up another.
Install
| You use | Command |
|---|---|
| Python | pip install agent-countercheck · uvx --from agent-countercheck countercheck verify |
| macOS / Linux, Homebrew | brew trust --formula datacrate360/tap/agent-countercheck && brew install datacrate360/tap/agent-countercheck |
| macOS / Linux, any shell | curl -fsSL https://raw.githubusercontent.com/datacrate360/agent-countercheck/PROD/install.sh | sh |
| Windows 10/11, PowerShell | irm https://raw.githubusercontent.com/datacrate360/agent-countercheck/PROD/install.ps1 | iex |
Every install gives you the countercheck command.
Countercheck v0.3.1
Hermes desktop app / gateway users: upgrade; with v0.3.0 the hook ran no checks there. Then approve the hook once
in a terminal (docs/hooks/hermes.md).
-
Recipes:
recipes/with a first recipe, Oracle → Snowflake (check files, helper scripts, a tested
5-minute start). README FAQ and diagrams. -
Fixed: the Hermes hook did nothing when Hermes ran outside the project. Hermes sends its own working directory
ascwd, and the desktop app and gateway run from the home folder, sohermes-pre-verifyfound no git repository
and silently let every turn finish. It now checks the git repositories holding the turn'schanged_pathsthat
have a.countercheck/checks.toml(each one; a failure names its project), and stays silent when the changed files
are in no such project. A project counts if its check file is in the working tree or the base revision, so
deletingchecks.tomldoesn't skip the checks. Withoutchanged_paths,cwdis used as before. -
Docs: docs/hooks/hermes.md: setup, which project gets checked, and the one-time hook
approval Hermes needs (hermes hooks listshows "not allowlisted" until you answer y in a terminal; until then the
hook never runs).
Install
| You use | Command |
|---|---|
| Python | pip install countercheck · uvx countercheck verify |
| macOS / Linux, Homebrew | brew install datacrate360/tap/countercheck |
| macOS / Linux, any shell | curl -fsSL https://raw.githubusercontent.com/datacrate360/countercheck/PROD/install.sh | sh |
| Windows 10/11, PowerShell | irm https://raw.githubusercontent.com/datacrate360/countercheck/PROD/install.ps1 | iex |
Countercheck v0.3.0
- Native Windows support (Windows 10/11, x86_64):
countercheck.exeruns without WSL. Install with
pip install countercheck,uvx countercheckorirm .../install.ps1 | iex(checks the SHA-256 against
SHA256SUMS.txtbefore installing). What works:verify,show,init,report,ui, the three hooks
(claude-stop,hermes-pre-verify,session-start) with Windows paths, every check type. Checks run with Git for
Windows'shby default (a missingshfails the check with a plain message); each check runs in a Job Object,
so a timeout kills the whole process tree and background leftovers stop when the check ends;linkfolders become directory junctions; symlinks Windows refuses are written as text files with the
target, like git does. Paths and the tree hash use/, so the same work gives the same receipt paths and tree
hash on Windows and Unix. CI tests every change on Windows. What doesn't, yet: no sandbox on Windows (checks
run as a normal process; the receipt saysnone (no sandbox on Windows yet),--sandbox seatbeltis a plain
error); no ARM64 build (the x86_64 one runs under emulation); no winget or scoop. shelloption on a check:"sh"(default),"pwsh"(PowerShell 7, or Windows PowerShell) or"cmd"
(Windows). Validated when the check file is read.- Fixed: Windows line endings. A
$at the end of acoverage(orforbid) pattern now also matches before
\r\n; before,coveragereported items in CRLF files as missing. countercheck ui: a live page of the receipts that updates while an agent works. 127.0.0.1 only, a random
token in the address,Hostchecked (so other web pages and DNS rebinding can't read it), GET only, read-only, a
strict Content-Security-Policy with a per-page nonce. Port 7357, or any free port when taken (--portto choose);
a secondcountercheck uireuses the running one. No web framework: the Rust standard library only.countercheck report: one self-contained HTML page of every saved run (verdict, the agent's claim, each check
and its output, protected changes, seal status, a targets × checks grid for matrix checks). No scripts and no
outside resources (enforced by a Content-Security-Policy); every value is escaped, because receipts carry text from
the agent's files.--openopens it in the browser.
Install
| You use | Command |
|---|---|
| Python | pip install countercheck · uvx countercheck verify |
| macOS / Linux, Homebrew | brew install datacrate360/tap/countercheck |
| macOS / Linux, any shell | curl -fsSL https://raw.githubusercontent.com/datacrate360/countercheck/PROD/install.sh | sh |
| Windows 10/11, PowerShell | irm https://raw.githubusercontent.com/datacrate360/countercheck/PROD/install.ps1 | iex |
Windows needs Git for Windows. Every download is listed in SHA256SUMS.txt.
Countercheck v0.2.1
Upgrade note: add the new session-start hook next to your existing stop hook (snippets in the README, "Agent
hooks"). Without it, hooks behave as in v0.2.0.
-
Security:
countercheck hook session-start(Claude Code / Cortex CodeSessionStart, Hermes
on_session_start). The stop hooks locked the checks toHEAD, so an agent that committed an edit to a
protected test or tochecks.tomlmoved the "locked" rules with it and was let through. The new hook records the
commit each session started from (in.git/countercheck/sessions/), and the stop hooks lock the checks to it.
Add it to your hook settings (README "Agent hooks"). The benchmark was not affected: it pinned the base with
COUNTERCHECK_BASE. -
network = trueon a check lets it use the internet under the macOS sandbox (writes stay confined; the receipt
marks it). A check that fails while running without the internet gets a hint; the verdict stays FAILED, so a
worker can't soften a failure by making a blocked network call on purpose. This removes the benchmark's only
false alarms (checks that call a real website). The hint only appears when the output looks like a connection
error. -
Contributors:
docs/architecture.md(the rules and one run step by step), step-by-step guides in
.claude/skills/, a new exampleexamples/ml-eval-threshold/, DCO sign-off for pull requests.
Install / upgrade: brew upgrade countercheck · pip install -U countercheck · uvx countercheck@latest · curl -fsSL https://raw.githubusercontent.com/datacrate360/countercheck/PROD/install.sh | sh
Countercheck v0.2.0
Upgrade if you use Countercheck on Linux. In v0.1.0, when a check ran past its time limit, Countercheck
stopped it with kill -KILL -<process group>. The kill in procps-ng 4.0.4 (Ubuntu 24.04, Debian 13 and others)
can misread that and signal every process the user owns. v0.2.0 uses kill -s KILL -- -<group> and never signals
group ids of 1 or less; a test checks that a timed-out check's whole process group really stops. macOS was not
affected.
New
- Check types (docs/checks.md):
matrix(one check over many targets, a result per target),
[[forbid]](patterns that must not appear, reported asfile:line),golden(output must match a locked
reference: exact text, or CSV with a numeric tolerance and optional row sorting),budget(a printed
METRIC name=valuewithin limits) and[[coverage]](every item in a locked list is found). Target lists,
reference files and inventories must be protected, so a worker can't change what it is judged by. - Example: examples/data-migration/, a made-up two-tenant migration that uses
every check type and fails for four explained reasons. - Claude Code hook:
countercheck hook claude-stopblocks Claude Code's stop while the checks fail and passes the
reasons back (docs/hooks/claude-code.md). Cortex Code reads the same hook format.
A loop guard (COUNTERCHECK_MAX_NUDGES, default 3) means it never blocks forever. - Honest blockers: both hooks let an agent stop when its final message honestly says the work can't be
finished (and it changed no protected file). The FAILED receipt is kept. Claims of success and any protected-file
change are still pushed back. - Sandbox (macOS): localhost is allowed, so web tests with a local server and a browser work; the internet
stays blocked.
Fixed
- A receipt that recorded a cost (
--cost-usd) could read back as BROKEN incountercheck show(a float lost one
bit in JSON). Numbers now round-trip exactly. - Protected-file matching resolves
./,..and empty path segments first, so./tests/ais protected and paths
outside the repository never match. - The PyPI source package includes the LICENSE file.
Tests
69 → 136 tests on macOS (134 on Linux), including property tests and tests that catch every surviving mutant
found by mutation testing in the receipt, claim and config code.
Install / upgrade: brew upgrade countercheck · pip install -U countercheck · uvx countercheck@latest · curl -fsSL https://raw.githubusercontent.com/datacrate360/countercheck/PROD/install.sh | sh
Countercheck v0.1.0
Full Changelog: https://github.com/datacrate360/countercheck/commits/v0.1.0