Skip to main content

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 baseline records today's accepted matches in .antibodies/baseline.json, keyed by the matched code rather than line numbers. antibody check then fails only on matches beyond it. A pasted copy of an accepted line still counts as new.
  • antibody check --since origin/main looks 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 --auto with 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

MIT

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)

Source distribution for antibody 0.2.0
File Size Uploaded Provenance
antibody-0.2.0.tar.gz 137.7 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for antibody 0.2.0
File Interpreter ABI Platform
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.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page