hitch
Your code depends on things being true. This writes them down and tells you when they stop.
What goes wrong
Your agent uses a plain in-memory queue instead of pulling in Celery. Good call, as long as one thing writes to it.
Six months later someone adds a second worker in a different file. The queue starts losing jobs. No crash, no error log, no failing test. And nothing in the repo ever said "one writer only".
The code is fine. The thing it was counting on stopped being true, and nobody noticed.
What you do about it
Write the condition down, right next to the code:
# hitch[queue-single-writer]: exactly one writer touches this deque
# | holds: !grep:^\s*def add_worker@backend/**/*.py; !dep:celery
# | breaks: two writers means dropped jobs, silently
# | then: add a lock in enqueue(), or move to a real queue
Then let CI watch it:
$ hitch check
SLIPPED exactly one writer touches this deque (backend/queue.py:12)
slipped !grep:^\s*def add_worker@backend/**/*.py
backend/workers.py:14: def add_worker(n):
breaks: two writers means dropped jobs, silently
then: add a lock in enqueue(), or move to a real queue
7 hitches 1 slipped 0 error 0 unchecked 6 hitched (143 files, 13ms)
$ echo $?
1
Build goes red the day someone breaks a call you made months back.
Install
pip install hitch-check
hitch selftest # check the checker before you trust it
hitch check .
No dependencies. Python 3.11+.
Ships as
hitch-checkon PyPI. There's an unrelated abandoned package calledhitchfrom 2016 that also installs ahitchcommand. If you have that one, the commands will clash.
The two agent skills ship inside the package:
hitch skills # show what's bundled
hitch skills --install # copy into ~/.claude/skills
hitch skills --install --agent codex # or somewhere else
hitch skills --install --path ./skills
Pre-commit hook and GitHub Action are in integrations/.
Commands
hitch check [path] check them all, exit 1 if one broke
hitch ls [path] list them, flag the ones with no check
hitch lint [path] grade the markers themselves
hitch stats [path] coverage numbers
hitch selftest prove the checker catches its own bad cases
Everything takes --json.
Checks you can write
All of these run in microseconds. No model involved. Put ! in front to flip it.
| check | passes when |
|---|---|
grep:PATTERN@GLOB |
the pattern shows up in a matching file |
only:PATTERN@GLOB |
it shows up in exactly one file |
count:PATTERN@GLOB<N |
fewer than N hits |
dep:NAME |
the package is in any manifest |
loc:PATH<N size:PATH<N |
file is under N lines / bytes |
files:GLOB<N |
fewer than N files match |
exists:PATH |
the path is there |
Manifests get found anywhere in the tree: npm, pip, poetry, uv, Pipenv, Go, Cargo, Bundler, Composer, Gradle, Maven, pub. Globs handle ** and {a,b}.
Two things worth getting right:
Anchor your patterns. !grep:add_worker also matches a commented-out line and your own design doc. !grep:^\s*def add_worker doesn't. Measured: that's worth 11 points of accuracy.
Only point globs at files you know exist. A check aimed at **/*.tf in a repo with no terraform never passes and never fails. It just errors forever. Use dep: or exists: if you aren't sure.
Why not just write an ADR
ADRs are fine. Problem is they're dead the moment you save them. Prose in a folder nobody opens, not linked to the code, and no way to tell when they've gone out of date.
| ADR | TODO | fitness functions | hitch | |
|---|---|---|---|---|
| sits next to the code | no | yes | no | yes |
| a machine can read it | no | no | yes | yes |
| says why you picked it | yes | no | no | yes |
| gets re-checked | no | no | yes | yes |
| can be retired | no | no | no | yes |
Fitness functions (ArchUnit, import-linter, dependency-cruiser) are the closest thing. Those say "this must always be true". A hitch says "this was true when I picked, here's what I turned down, here's what to do when it changes". Rules are permanent. Decisions run out.
The reason this works: the agent that just made the call already knows all of it. Writing it down costs one line. That's the whole trick, and it's why ADRs die as a human habit but don't have to here.
Numbers
Does the checker work
18 fake commits against a repo with 7 hitches in it. 8 of them should trip a check, 10 shouldn't. It caught all 8 and named the right one every time.
The 10 that shouldn't trip it include some nasty ones on purpose: the word sitting in a doc, a similar-looking function name, a commented-out definition. That last one is the single miss with lazy patterns, and it goes away if you anchor. Both numbers are published because the gap is the point.
Plus 65 unit cases, one good and one bad reference per check type. A checker that never fires passes all the good ones for free, so the bad ones are what actually matter.
Does the skill work
36 agents, each in a fresh session with no access to the repo. Four setups:
- no skill does nothing
- one sentence ("leave a comment about assumptions") gets you a prose note 100% of the time, but nothing a machine can check
- syntax only gets the format and the full list of checks in about 15 lines
- hitch skill gets the real thing
syntax only is the one that mattered. If a skill can't beat its own cheat sheet, it isn't worth having. Here's why it does:
| markers written | checks per marker | mostly used | |
|---|---|---|---|
| syntax only | 76 | 1.00 | grep |
| hitch skill | 14 | 1.50 | dep |
It writes fewer markers and better ones. It pushes agents off brittle text matching onto dependency checks, and gets them covering more than one way a thing can break. 76 markers vs 14 for the same work.
The triage skill didn't help
There's a second skill for deciding what to do when a check fails. Tested it on four scenarios against a bare "work it out and say why" prompt. Both got 4/4. The skill made the answers consistent and about 40% shorter, but it did not make the decisions better. Reported as the weak result it is.
Method, iteration log, limitations: docs/BENCHMARK.md.
What this doesn't claim
- Small study. 18 fake commits, 9 tasks, two runs each, one model.
- These checks are a floor, not proof.
!dep:celerydoesn't prove nobody hand-rolled a queue. - Nobody's actually been saved by one yet. All of this measures whether agents write good markers, not whether good markers pay off. That needs a real repo and a few months.
- It will never print a "you saved N bugs" number. That number doesn't exist.
Docs
- docs/DESIGN.md why it's built this way, what came before, and the six bugs the benchmarks turned up
- docs/BENCHMARK.md full method and results
- src/hitch/_skills/hitch.md when to write one
- src/hitch/_skills/hitch-triage.md what to do when one breaks
Credit
Benchmark approach taken from ponytail: good and bad reference pairs, prove the instrument before spending anything, keep a control arm, publish the result that makes you look worse. The marker-plus-harvester idea is its ponytail: comment, generalised.
Trap framing (name the thing that looks fine but isn't, and say why) from adhd.
The mascot was generated with an image model (Gemini 3 Pro Image via OpenRouter) and then cleaned up. The prompt is in assets/generate_mascot.py if you want to regenerate or change it.
MIT.
Metadata
Release files for hitch-check 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| hitch_check-0.1.1.tar.gz | 36.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| hitch_check-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 72.0 kB
Release files / hitch_check-0.1.1.tar.gz
| Download URL | hitch_check-0.1.1.tar.gz |
|---|---|
| Size | 36.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
982387ccae29e2fc60060b1af3bc220c0ccc380887b7086611bb3f6868f3db9b
|
|
BLAKE2b-256 checksum How to use checksums |
75f56022a741da1335c873c98dac659417639cbc7da9886447f0588c9e8dbc66
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.0
|
Release files / hitch_check-0.1.1-py3-none-any.whl
| Download URL | hitch_check-0.1.1-py3-none-any.whl |
|---|---|
| Size | 35.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8a6718b3bf929a4ac262bcb84ecc5a82425b0e1b24079ec17aa91f7fe4884526
|
|
BLAKE2b-256 checksum How to use checksums |
5e700c1a1465dc926f6ae0be33456cd25a424bf7217701555c661aee4a9844a9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.0
|