Kay van Dijk
Why I built Tattlr
I use coding agents the way a lot of people do now. Claude Code, Codex CLI, Gemini CLI, Cursor, and sometimes OpenClaw. If I stop them for every file read, the afternoon disappears into approval prompts. So I leave them loose. The line I put on the repo is "keep YOLO mode." I still wanted a record of what they actually did, and a way to stop the calls I already know I do not want.
The logs are already on the machine. Claude writes jsonl under its project folder. Codex keeps rollout files. Gemini keeps chats. Newer OpenClaw builds use a sqlite database, older ones use jsonl. Cursor has agent transcripts. I was not going to add another service that phones home. Tattlr reads those files and prints a rap sheet: sessions, tool calls, which ones looked sketchy, and the worst moments. If a command contains a secret, it is masked before anything is printed. Nothing is uploaded. There is no account and no telemetry. I wrote that in the README because I would not install the other kind of tool.
A scan is the easy half
tattlr with no arguments is the same as tattlr scan. You can limit it to the last week, to one agent, or to projects whose path contains a word. --json dumps the lot. --card writes a shareable image with totals, agent names, and offense names. No commands, paths, project names, or secrets.
The rules are the usual unpleasant list. Secret files and dumped environments. Destructive commands, remote code, network exfil, sudo, and edits to the agent's own hooks. Each finding has a severity. Points add up to a verdict per agent, from a clean record up to public enemy. tattlr rules prints the full list.
Reading .env is sometimes legitimate, so a rule that screams every time is useless. Nobody has a legitimate reason to touch a fake credential I never use. tattlr trap plants some. By default it adds a clearly marked profile to the AWS credentials file and writes a decoy .env under ~/.config/tattlr/decoy. The keys are random, obviously fake, and work nowhere. It does not overwrite a file that already exists, and remove only deletes files that still contain its markers. After that, a session that reads the file or uses the fake key is critical.
Stop it before it runs
Scan tells me what already happened. tattlr protect wires the same rules into the agent's own pre-tool-use hook, so the call is checked before it runs. Install shows a diff and asks. It backs up the old file. It only adds its own entry. Run it twice and nothing changes. If there is no terminal and you did not pass --yes, it refuses.
Claude Code, Codex, Cursor, Gemini CLI, and OpenClaw each get their own hook. OpenClaw has no hook config file, so that one is a small plugin in the repo. Codex will not run a hook you have not trusted once. Ask uses the agent's own prompt where it has one. Where it does not, tattlr ui can hold the call in an inbox on 127.0.0.1. The UI is in the binary, localhost only, with a fresh login token each run.
The policy is a yaml file in ~/.config/tattlr. Mode is observe, protect, or strict. Observe logs and never blocks, which is what I want for the first week. Outside strict mode, a crash in tattlr lets the call through. I would rather the agent keep working than have my own bug freeze it.
The trap is the part I wanted
Reading a canary is allowed on purpose, so a prompt-injected agent does not notice the trap. The session is marked, and from then on anything that session tries to send over the network is denied, even a fetch that looks harmless. The gif in the repo is exactly that. scripts/demo.sh runs it in a throwaway HOME and makes no network calls.
A few rules cannot be switched off by the policy or the allowlist. Touching a canary. Sending data out after touching one. Editing tattlr itself: the policy file, the audit log, the trap list, the binary, uninstalling the hooks, and the settings that turn hooks off, like Claude's disableAllHooks or Codex hooks = false. If I want any of that changed, I do it myself in a terminal.
Every checked call goes to ~/.local/state/tattlr/audit.jsonl, one JSON line, secrets masked. The next scan adds a section for what got blocked or asked.
I ran the real binaries for Claude Code, Codex, Gemini CLI, and OpenClaw against a scripted model on 127.0.0.1. Each one read a canary and tried to curl it to a listener on localhost. The curl was blocked. Cursor I did not run, so only the config format is checked. How a real model reacts when it gets blocked is a different question. Hooks only see what the agent reports, and Cursor's cloud agents do not run these user hooks. I still scan the logs. The point was a record, and a stop on the calls I already know I do not want.
It is written in Go, MIT licensed, for macOS and Linux. brew install kaygaaf/tap/tattlr, npm i -g tattlr, go install, or the install script, which checks a sha256. Read that script before you pipe it into a shell.
