v0.1.1 · MIT · Claude Code & Cowork

Stop AI coding agents from quitting early.

AntiStallClaude is a harness-enforced Stop hook that blocks the “announce-then-halt” drift — where an agent ends a turn with unfinished, authorized work. Advice in a config file gets rationalized away. A hook can’t be.

On 0.1.0? Update to 0.1.1 — 0.1.0 had a Stop-hook bug that could loop and burn tokens. Replace antistall-gate.py or re-run install.py.

The problem

Autonomous agents stall mid-task. With work still queued and nothing actually blocking them, they write a tidy “here’s what I did, next I’ll…” summary — and stop. The tell is consistent: the moment the agent composes that summary, it has already decided to quit.

A CLAUDE.md rule

Advice. It lives in the model’s context and loses to in-the-moment reasoning (“clean checkpoint”, “the turn is long”).

A memory note

Same advice, different file. Degrades over a long session.

A system-prompt nudge

Still text the model weighs — same failure class.

A Stop hook ✓

A separate process the harness runs every turn-end. The model can’t reason its way past code that isn’t its to run.

How it works

While a sprint is armed, ending a turn is blocked unless the agent writes a fresh, single-use stop-ticket declaring a legitimate reason:

DONE — whole queue finished BLOCKED — human-only decision QUESTION — needs an answer

agent finishes a turn ─▶ harness runs antistall-gate │ sprint armed? ──── no ──▶ allow (silent) │ yes stop_hook_active? ── yes ──▶ allow (loop guard — never block a continuation a block caused) │ no fresh (< max-age) DONE/BLOCKED/QUESTION ticket? ── yes ──▶ consume, allow [any ticket file is single-use: consumed on read, fresh or stale] │ no per-session block count ≥ cap, or counter anomaly? ── yes ──▶ allow (fail-open escape) │ no ▼ { "decision": "block", "reason": "KEEP WORKING …" } ─▶ agent continues

The gate can never loop or trap a session. It honors stop_hook_active — always allowing a stop that a prior block caused, so it nudges a drift-stop at most once per continuation chain. A per-session, fail-open anti-loop cap (default 6) is a second, independent guard: the counter is keyed on the session id so two agents never share it, and any missing/empty/corrupt/unreadable/unwritable counter allows the stop rather than blocking again. When no sprint is armed, the gate is silent — normal chat is never gated.

Install

git clone https://github.com/scottconverse/AntiStallClaude.git
python3 AntiStallClaude/install.py /path/to/your/project   # defaults to the current dir
# then RESTART the Code tab — hooks load at session start

Cowork note: user-level ~/.claude hooks don’t fire in Cowork — the installer puts everything in the project .claude/, which does.

Use it

python3 .claude/hooks/antistall.py arm      "build the export pipeline"
python3 .claude/hooks/antistall.py done     "shipped + tests green"   # disarms
python3 .claude/hooks/antistall.py blocked  "need prod DB creds"      # stays armed
python3 .claude/hooks/antistall.py question "which storage backend?"
python3 .claude/hooks/antistall.py status

Honest about limits

It is not unspoofable — an agent could write a false DONE ticket. But that converts a silent drift into an explicit, logged, auditable claim, and default-deny removes the lazy path. It enforces don’t stop early, not do good work — pair it with tests and CI.