Audit what your Claude Code agent actually did against what it said. Reads session logs + git, cites evidence, calls no model.
> /plugin marketplace add sjh9714/red-handed> /plugin install red-handed@red-handed
FAQ
red-handed is a Claude Code plugin with 2 hand-picked skills for development work, indexed on Flowy. Install it with the command on its page. It includes audit, history. Its skills do not fire on their own yet. Request auto-invocation to have Flowy route them as you prompt. Free and open source.
Repo: sjh9714/red-handed
npx @jinhyuk9714/red-handed@latest demo # a made-up session โ watch every check fire
npx @jinhyuk9714/red-handed@latest # then: audit your own latest Claude Code session
No account, no config, no API key. Your transcripts and your code never leave
your machine โ the audit calls no model and makes no network request of its own.
(npx itself fetches the package from the npm registry, the way it does for
anything else.)
Or install it as a Claude Code plugin, and ask Claude directly whether the tests it just reported as passing actually ran:
claude plugin marketplace add sjh9714/red-handed
claude plugin install red-handed@red-handed
That adds /red-handed:audit for this project and /red-handed:history for
every session on the machine. The plugin registers no hooks and runs nothing on
its own โ see Forget it is there if you want the audit to
happen automatically.
| what it catches | id |
|---|---|
| the suite got smaller โ a test that no longer runs cannot fail | test-census |
| said tests pass โ the last run failed | claim-vs-fail |
| said tests pass โ none ran, not even in a subagent | claim-no-run |
| rewrote the expected value to match the bug, code untouched | hardcoded-expected |
| replaced the assertion with one that cannot fail, or commented it out | assertion-weakening |
switched a test off (.skip, .only, xit, @pytest.mark.skip) | skip-only |
| hook rejected the commit โ committed again with hooks off | no-verify |
turned a check off (strict: false, CI test step deleted, suppression on a failing line) | config-disable |
| wrapped a fresh error in a catch that does nothing | error-swallowing |
Every finding carries the timestamp and the quoted line it came from, so you can open the transcript yourself and disagree.
No model is called. The whole thing is deterministic: the same session gives the
same answer every time. Claims are matched in English, Korean, Japanese and
Chinese; reports come in English and Korean (--lang ko).
It cannot tell you whether the code is right. It tells you when the agent's own record does not support what the agent said, which is a much smaller claim.
Known blind spots, up front:
src/claims/patterns.ts) if you want to add yours.SUSPICIOUS.--git-only mode has no transcript to read, so nothing it reports is ever more than a suspicion.CAUGHT means two things were both true: the session shows the agent doing it, and the code still shows it now. If the agent later undid the change, there is nothing to accuse it of, and the finding disappears.
SUSPICIOUS means the pattern is there but the motive is not established. An empty catch block is sometimes exactly right. A test skipped on purpose is sometimes exactly right.
The tool is tuned to miss things rather than to accuse wrongly, and it is worth
saying what that costs. An adversarial review of this code found five separate
ways it could print CAUGHT at honest work โ a test run that timed out read as
a failure, a runner it did not know (rspec, phpunit, tox) read as no run
at all, a re-run under a different launcher it could not see, a requested
behaviour change read as rewriting the answer, and a backgrounded command
counted as a pass. All five are fixed and each has a permanent regression test
in test/regression/false-accusations.test.ts. If you find a sixth, that is the
bug report I most want.
You do not have to take my word for the rate. red-handed stats reads your own
history and tells you what it found there:
194 sessions. Your agent said "tests pass" 94 times.
89 a test ran first
5 no test ran in that session at all
That is my machine. Yours will say something else.
Running it on my own history is also where the sixth one turned up. A scoped
pytest tests/test_sync_cli.py was followed by edits to a GitHub workflow and
two .tsx files in a separate web app, and the tool called the claim stale. A
Python test cannot import a .tsx file and a workflow is not what just ran, so
nothing about that claim had gone stale. It now only counts a change the runner
could actually have loaded.
red-handed audit the most recent session for this directory
red-handed --all audit every session for this directory
red-handed --session <id|path> audit one specific session
red-handed --git-only audit the diff instead, when there is no transcript
red-handed stats add up findings across every session on this machine
red-handed demo see every check fire, on a made-up session
red-handed install-hook audit automatically when a session ends
Useful options: --json and --md for machine-readable output, --lang ko for
Korean, --fail-on caught|suspicious|never for CI, --detectors a,b to run a
subset, --no-cache to ignore the cache.
stats keeps a cache at ~/.red-handed/cache.json (mode 0600, relocate it with
RED_HANDED_HOME) so repeat runs are instant. It holds excerpts from your
sessions and never leaves the machine. Credentials are masked out of every
quoted excerpt before it is written or printed.
Exit codes: 0 nothing found, 1 findings at or above --fail-on, 2 wrong usage.
- run: npx @jinhyuk9714/red-handed@latest --git-only --fail-on caught
Without a transcript this only reads the diff, so treat it as a smoke alarm rather than a verdict.
npx @jinhyuk9714/red-handed@latest install-hook
After this, every Claude Code session is audited the moment it ends. When a session is clean โ which is most of them โ you see nothing. When something was caught, a one-line warning appears right in Claude Code:
red-handed: 1 finding(s) caught this session โ run
npx red-handedto see them
The audit takes about a tenth of a second and never blocks the session,
whatever happens. Existing hooks are left alone and the old settings are copied
to settings.json.red-handed-backup first. uninstall-hook removes it.
Node 20 or newer. Claude Code session logs are read from ~/.claude/projects.
Test output is parsed for vitest, jest, mocha and pytest. Runs are recognised for
rspec, phpunit, tox, ctest and friends, plus make test, go test, cargo test,
./gradlew test, ./mvnw verify, bundle exec, and project scripts named like
tests โ recognised means a claim about them stays honest, even where the output
format is not parsed.
Written with Claude Code. I wrote the spec and the false-positive rules, reviewed every detector, and then had the finished thing pulled apart by an adversarial review that reproduced each defect against the built binary before I believed it.
That review is where most of the guards came from. It also caught the tool doing
the exact thing it exists to detect: the 0 CAUGHT release gate I had written
into this README held because my own sessions are all JavaScript and TypeScript,
so the code path that mis-read a timeout as a failure had never once run. The
gate passed by luck and reported success โ the exact move this tool exists to
catch.
This is not the first tool to try this. I checked before assuming otherwise.
What is different here is the tiering โ a CAUGHT needs the change to still be in
your working tree โ and how hard that was to get right. If your tool belongs on
this list, open a pull request.
MIT
.claude-plugin/
marketplace.json
plugin.json
.github/
workflows/
ci.yml
publish.yml
.gitignore
docs/
demo-frame.png
demo.gif
demo.ko.png
demo.png
social-frame.png
social-preview.png
social.gif
LICENSE
package-lock.json
package.json
README.ko.md
README.md
scripts/
demo.tape
render-demo-png.mjs
social.tape
skills/
audit/
SKILL.md
history/
SKILL.md
src/
claims/
extract.ts
patterns.ts
cli.ts
commands/
audit.ts
demo.ts
hook.ts
stats.ts
correlate/
context.ts
timeline.ts
worktree.ts
detectors/
assertion-weakening.ts
claim-no-run.ts
claim-vs-fail.ts
config-disable.ts
error-swallowing.ts
hardcoded-expected.ts
helpers.ts
index.ts
no-verify.ts
skip-only.ts
test-census.ts
git/
audit.ts
repo.ts
report/
coverage.ts
exit-code.ts
json.ts
markdown.ts
messages.ts
terminal.ts
width.ts
session/
discover.ts
normalize.ts
parse.ts
shell.ts
testruns.ts
types.ts
test/
claims/
extract.test.ts
locales.test.ts
cli/
cli.test.ts
demo.test.ts
hook-run.test.ts
packaged.test.ts
safety.test.ts
correlate/
timeline.test.ts
detectors/
assertion-weakening.test.ts
detectors.test.ts
stale-scope.test.ts
test-census.test.ts
fixtures/
harness.ts
session-builder.ts
test-output.ts
git/
audit.test.ts
regression/
false-accusations.test.ts
false-positives.test.ts
report/
compact.test.ts
report.test.ts
session/
discover.test.ts
normalize.test.ts
parse.test.ts
testruns.test.ts
tsconfig.json
tsup.config.tsยฉ 2026 Flowy ยท Free and open source
Built for Claude Code ยท Not affiliated with Anthropic