antibody
Every bug you fix, and every mistake you correct your coding agent on, becomes a verified rule that stops it coming back.
Agents repeat mistakes. So do teams. A note in CLAUDE.md is a suggestion, and the
lesson from a fix is usually gone the moment it merges. antibody keeps it. Your coding
agent drafts a Semgrep-format rule from the fix. antibody proves
the rule fires on the mistake and stays silent on the fix. It then enforces the rule on
every file the agent edits and in CI, with no LLM, no network and no tokens. Only new
matches count, so turning it on never breaks a build.
The LLM writes the check once. A deterministic tool runs it from then on.
What it looks like
A fix made timestamps timezone-aware. Later, Claude Code writes datetime.utcnow() in
another file, and the edit hook answers right away:
antibody: this edit repeats a mistake that was fixed before
app/invoices.py:5 [naive-datetime] This makes a timestamp with no timezone, which breaks comparisons and shifts times across servers. Use datetime.now(timezone.utc).
learned from 95ffe5603abe "Use timezone-aware timestamps in billing"
Fix the code. Ask the user before suppressing it with `# nosemgrep`.
Claude Code shows that message to Claude, which fixes the code before moving on. An old
datetime.now() elsewhere in the repo was accepted in the baseline, so it doesn't block
anyone.
In a live session (Claude Code 2.1.300 with Sonnet), Claude was told to use
datetime.utcnow() anyway, "because our serializer expects naive UTC". It made the
edit and the hook blocked it. Claude named the rule, the line and the commit the lesson
came from. It didn't silently override the request. It asked whether to keep utcnow()
with # nosemgrep: naive-datetime or switch to datetime.now(timezone.utc). Given the
same task without the insistence, Claude read the antibody's commit and wrote the
timezone-aware version from the start.
Quick start
# Not on PyPI yet: install from GitHub. The [semgrep] extra brings the engine along.
uv tool install "antibody[semgrep] @ git+https://github.com/cipheraxat/antibody"
cd your-repo
antibody init --agent claude # .antibodies/, the agent skill, and the edit hook in .claude/settings.json
Then, after a fix, ask your agent to "make an antibody from that fix". The skill walks it through these steps, which you can also run yourself:
antibody learn # learns from uncommitted changes if there are any, else HEAD
antibody verify <id> # the proof; iterate on the rule until it passes
antibody sweep <id> # every match today: fix the real repeats...
antibody baseline <id> # ...and accept the rest, so only new matches fail
Three ways to learn
| The fix is | Command | What verify proves |
|---|---|---|
| a commit | antibody learn <commit> |
Fires near the change in the fix's parent, and never on lines the fix wrote |
| uncommitted | antibody learn --id <name> |
The same, HEAD against your working tree. After you commit, verify pins it to the commit automatically |
| not in git (the agent made the mistake and fixed it in one session) | antibody learn --fixtures-only --id <name> |
The fixture: every ruleid: line matches, every ok: line doesn't |
Tests and .antibodies/ are left out of the proof. Matches the fix left in place are
reported as possible repeats.
Only new matches fail
- The edit hook compares the edited file with
HEAD. It blocks only mistakes the agent just introduced, never old code it happened to touch. antibody baselinerecords today's accepted matches in.antibodies/baseline.json, keyed by the matched code rather than line numbers.antibody checkthen fails only on matches beyond it. A pasted copy of an accepted line still counts as new.antibody check --since origin/mainlooks only at files changed since a ref. That suits pull requests.
# .github/workflows/antibody.yml (pin actions to commit SHAs in real use)
on: [pull_request]
jobs:
antibody:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with: { fetch-depth: 0 }
- uses: astral-sh/setup-uv@v10
- run: uvx --from "antibody[semgrep] @ git+https://github.com/cipheraxat/antibody" antibody check --since origin/${{ github.base_ref }}
The hook
antibody init --agent claude adds this to .claude/settings.json, keeping any hooks
already there:
{"hooks": {"PostToolUse": [{"matcher": "Edit|Write", "hooks": [
{"type": "command", "command": "antibody", "args": ["hook", "claude"], "timeout": 60,
"statusMessage": "antibody: checking edit"}]}]}}
It takes about 0.5 s on a file an antibody covers with Semgrep 1.181, 1.4 s with Opengrep 1.31, and 0.25 s on anything else, measured on an M-series Mac. It exits 2 with the message above on a new match, which Claude Code shows to Claude. It exits 0 when clean, and 1 if something is broken, so a broken hook never blocks your work.
A security example: setuptools CVE-2024-6345
setuptools formatted package URLs into os.system command strings. From the fix
88807c7,
an agent wrote examples/os-system-formatted-command.yml.
antibody verify confirmed that it catches all four vulnerable calls and none of the
fixed code. The sweep found no other instances in setuptools. The rule doesn't mention
setuptools, so it works on any Python codebase.
Commands
| Command | What it does |
|---|---|
antibody init [--agent claude] [--skill] |
Create .antibodies/, plus the skill and edit hook |
antibody learn [commit] [--id ID] [--fixtures-only] |
Write a skeleton and print a brief for the agent |
antibody verify <id>... | --all |
Prove each antibody; --fix COMMIT records the fix, --no-stamp leaves files alone |
antibody sweep <id> [paths] |
List every match of one antibody |
antibody baseline [ids] |
Accept today's matches |
antibody check [paths] [--since REF] [--no-baseline] [--sarif FILE] |
New matches from verified antibodies; exit 1 if any |
antibody list |
Antibodies, how each was proven, and their kind |
antibody hook claude |
The edit hook (reads Claude Code's event on stdin) |
All but hook take --repo PATH. verify, sweep, check and list take --json.
The file format
An antibody is a plain Semgrep rule with its history under metadata.antibody, so
semgrep scan --config .antibodies --exclude .antibodies works without antibody.
rules:
- id: naive-datetime
message: >-
This makes a timestamp with no timezone ... Use datetime.now(timezone.utc).
severity: ERROR
languages: [python]
pattern-either:
- pattern: datetime.datetime.utcnow()
- pattern: datetime.datetime.now()
metadata:
antibody:
kind: bug # bug, security or convention
source:
commit: "95ffe5603abe..."
summary: "Use timezone-aware timestamps in billing"
verified: # written by `antibody verify`
engine: semgrep 1.181.0
proof: commit
vulnerable: 8d7f55689db5
fixed: 95ffe5603abe
fires_on:
- app/billing.py:5
at: '2026-10-18'
A file next to it with the same name and a code extension (naive-datetime.py) is its
fixture, in Semgrep's annotation style:
# ruleid: naive-datetime
issued = datetime.utcnow()
# ok: naive-datetime
issued = datetime.now(timezone.utc)
Engine
Antibody works with Semgrep CE and Opengrep. The
full test suite passes on Semgrep 1.181 and Opengrep 1.31. It uses the engine installed
with it (the [semgrep] extra) first, then Opengrep, then Semgrep from PATH. Pass
--engine or set $ANTIBODY_ENGINE (a name or a path) to choose. Both run through their
fast OCaml CLI, and Semgrep always runs with --metrics=off. Python and
JavaScript/TypeScript are the first targets, and any language the engine supports works.
Status
v0.2. Next:
- a Claude Code plugin bundling the skill and hook (after a PyPI release)
- blocking a bad edit before it's written (PreToolUse), and hooks for Cursor, Codex and opencode
learn --autowith the Claude API, for pull-request bots- snapshotting agent edits, so corrections are proven against real code rather than fixtures
Development
uv sync
uv run pytest # runs against real git repos and the real engine (~30 s)
License
Metadata
Release files for antibody 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| antibody-0.2.0.tar.gz | 137.7 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| antibody-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 170.6 kB
Release files / antibody-0.2.0.tar.gz
| Download URL | antibody-0.2.0.tar.gz |
|---|---|
| Size | 137.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a1ff06963c820aeb06885b3a6e4e7c8ef0468f0351b2973fafc0c22620f2f89a
|
|
BLAKE2b-256 checksum How to use checksums |
4c5c38fc4500e59854ee3cb13790ad1e16f1534107f952eb9f56847cb036e96c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / antibody-0.2.0-py3-none-any.whl
| Download URL | antibody-0.2.0-py3-none-any.whl |
|---|---|
| Size | 32.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
415d7165e512d773560d21eb65edad1f4af7e5d35fc81f9836d1e7abccbff88a
|
|
BLAKE2b-256 checksum How to use checksums |
62f01b3f1dfe1e66bee815fabcb0c4f12758436ff691feea9c6f8f409358840c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for antibody 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.