ai newsDi., 20. Okt.
EilmeldungDeepSeek gave away its engine. The $38-an-hour parts stayed home

Your AI agent says "done". Your tests say 3 failing. The Stop hook that settles it

You ask an agent to fix a bug. It edits four files, writes a cheerful summary, and says the task is complete. You run the tests and three of them fail. Sometimes it never ran them at all. Sometimes it ran them, saw red, and wrote "done" anyway. If you're running several agents in parallel, it's wor

dev.toDienstag 15:34 · 20.10.8 Min. Lesezeit
Bild: dev.to

You ask an agent to fix a bug. It edits four files, writes a cheerful summary, and says the task is complete. You run the tests and three of them fail. Sometimes it never ran them at all. Sometimes it ran them, saw red, and wrote "done" anyway.

If you're running several agents in parallel, it's worse: each one reports success on its own branch, and the feature still doesn't work when you put the branches together.

This isn't a model being lazy. An agent's "done" is a self-report, and the agent is grading its own homework. Adding "ALWAYS RUN THE TESTS" to your rules file in capital letters doesn't change that. A check that runs whether the agent wants it to or not does.

Each tool reads a different set of files. Here's the current picture, checked against each tool's docs this week:

The setup I use: one AGENTS.md as the source of truth, plus a tiny CLAUDE.md that imports it:

@AGENTS.md ## Claude Code only - Use plan mode before touching migrations or billing code. Enter fullscreen mode Exit fullscreen mode Claude Code expands @path imports at launch, so the AGENTS.md content ends up in context and you only maintain the rules in one place. Codex and Cursor read AGENTS.md directly.

For scoped rules, Cursor and Claude Code use different frontmatter keys for the same idea:

--- description: API route conventions globs: src/app/api/**/*.ts alwaysApply: false --- - Validate every request body with zod before touching the database. Enter fullscreen mode Exit fullscreen mode --- paths: - " src/app/api/**/*.ts" --- - Validate every request body with zod before touching the database. Enter fullscreen mode Exit fullscreen mode The first is.cursor/rules/api.mdc; the second is.claude/rules/api.md. Scoped rules only load when the agent works on matching files, so your backend rules stop eating context while you edit CSS.

This came up a lot this week: should AGENTS.md be committed? Yes. A simple test for each line in it:

Would a reviewer reject a PR that breaks this rule? If so, commit it. If it's just how you like to work, keep it local.

Commit: AGENTS.md, CLAUDE.md,.cursor/rules/,.claude/rules/,.claude/settings.json (where Claude Code hooks live),.cursor/hooks.json and the hook scripts themselves.

Keep local: "explain your plan before editing", "I prefer pnpm", your sandbox URLs, test accounts, tone preferences. These go in CLAUDE.local.md,.claude/settings.local.json, ~/.claude/CLAUDE.md, ~/.codex/AGENTS.md or Cursor's User Rules.

#.gitignore CLAUDE.local.md.claude/settings.local.json Enter fullscreen mode Exit fullscreen mode The gotcha: CLAUDE.local.md can switch off your AGENTS.md By default, Claude Code reads AGENTS.md only when there's no CLAUDE.md in the project. And a CLAUDE.local.md counts as one.

So if your team keeps everything in AGENTS.md with no CLAUDE.md, and you add a personal CLAUDE.local.md, Claude Code reads your local file instead of the team rules. Nobody else sees the problem, because their checkout doesn't have your local file.

Also note that Claude Code doesn't read AGENTS.override.md or AGENTS.local.md. Those are Codex conventions.

Here's the rule I'd tape to the monitor:

If breaking a rule causes a bug or an incident, it isn't a rule. It's a check.

"Prefer small functions" can stay as prose. "Tests must pass before you say done" can't, because the agent only follows it when it remembers to and agrees with itself that it applies.

Claude Code fires a Stop event when the agent finishes responding. If a Stop hook exits with code 2, Claude Code blocks the stop and sends the hook's stderr back to the agent as the reason. In other words, the agent gets your failing test output and has to keep working.

{ "hooks": { "Stop": [ { "hooks": [ { "type": "command", "command": "node \" $CLAUDE_PROJECT_DIR \" /.claude/hooks/require-green.mjs", "timeout": 300 } ] } ] } } Enter fullscreen mode Exit fullscreen mode Stop doesn't take a matcher, so there isn't one. The default command timeout is 600 seconds; set your own if your suite is slow.

#!/usr/bin/env node // Blocks Claude Code from finishing while the test suite is red. import { execSync, spawnSync } from " node:child_process "; import { readFileSync } from " node:fs "; const input = JSON. parse ( readFileSync ( 0, " utf8 " )); // hook JSON arrives on stdin // Pure Q&A turn with no changes in the working tree? Let it stop. const changed = execSync ( " git status --porcelain ", { encoding: " utf8 " }). trim (); if (! changed ) process. exit ( 0 ); const run = spawnSync ( " npm ", [ " test ", " --silent " ], { encoding: " utf8 ", shell: process. platform === " win32 ", }); if ( run. status === 0 ) process. exit ( 0 ); const tail = ` ${ run. stdout } \n ${ run. stderr } `. trim (). split ( " \n " ). slice ( - 40 ). join ( " \n " ); process. stderr. write ( ( input. stop_hook_active? " Still failing after the last attempt. \n ": "" ) + " The test suite is failing, so this task isn't done.

" + " Fix the code rather than editing or skipping the tests, then run npm test. \n\n " + tail + " \n " ); process. exit ( 2 ); Enter fullscreen mode Exit fullscreen mode Some notes from the docs:

The line "fix the code rather than editing or skipping the tests" is there on purpose. When an agent is under pressure to go green, the shortest path is sometimes it.skip or a looser assertion. Saying so in the block reason puts it right next to the failure.

Cursor has its own hooks system (.cursor/hooks.json ). Its stop hook can't block, but it can return a followup_message that Cursor submits automatically as the next user message, which works out to the same thing:

{ "version": 1, "hooks": { "stop": [{ "command": "node.cursor/hooks/require-green.mjs", "loop_limit": 3 }] } } Enter fullscreen mode Exit fullscreen mode //.cursor/hooks/require-green.mjs import { spawnSync } from " node:child_process "; import { readFileSync } from " node:fs "; const input = JSON. parse ( readFileSync ( 0, " utf8 " )); if ( input. status!== " completed " ) { console. log ( " {} " ); process. exit ( 0 ); } const run = spawnSync ( " npm ", [ " test ", " --silent " ], { encoding: " utf8 " }); if ( run. status === 0 ) { console. log ( " {} " ); process. exit ( 0 ); } const tail = ` ${ run. stdout } \n ${ run. stderr } `. trim (). split ( " \n " ). slice ( - 40 ). join ( " \n " ); console. log ( JSON. stringify ({ followup_message: `Tests are failing, so the task isn't done. Fix the code, not the tests:\n\n ${ tail } `, })); Enter fullscreen mode Exit fullscreen mode Project hooks run from the repo root, so use.cursor/hooks/... paths, not./hooks/.... loop_limit (default 5) caps how many automatic follow-ups one script can trigger.

Hooks run on one machine for one tool. A teammate on Copilot, a Codex cloud task, or you at 1 AM with hooks turned off all skip them. CI doesn't.

name: test on: pull_request: push: branches: [ main ] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - uses: actions/setup-node@v7 with: node-version: 24 cache: npm - run: npm ci - run: npm test Enter fullscreen mode Exit fullscreen mode Then make the check required on main (Settings → Branches or Rulesets → require status checks to pass). A workflow nobody is required to pass is just a suggestion with a green badge.

If you run parallel agents, this is also where the integration problem gets caught. Each agent's branch can pass on its own. Merge them into one integration branch (or use a merge queue) and let CI run the full suite on the combined result before anything lands on main.

Once tests are enforced, the rules file gets shorter and better. Use it to tell the agent what the checks are, so it runs them itself and the hook rarely has to fire:

## Definition of done - `npm test` passes and `npm run typecheck` passes. A Stop hook and CI enforce this. - Never skip, delete or weaken a test to make it pass. If a test is wrong, say so and stop. - In your summary, list the commands you ran and their results. Don't claim checks you didn't run. Enter fullscreen mode Exit fullscreen mode That last line is underrated. It turns "all done! ✅" into something like "ran npm test: 142 passed", which is a claim you can actually check.

If you want a starting point, I keep free, MIT-licensed rule sets for Next.js + Supabase + Stripe, Unity 6, Godot 4 and discord.js v14, in.mdc, AGENTS.md and CLAUDE.md formats:

👉 https://github.com/otctrading101/shiprules-free

If you'd rather not wire the hooks up yourself, the paid packs add the full rule sets plus ready-made Claude Code hooks, a zero-dependency scanner and a CI workflow at shiprules.gumroad.com. Code LAUNCH30 takes 30% off through Oct 22.

What's the most creative way you've seen an agent get to "done"? Tell me in the comments.

Disclosure: I make ShipRules (the paid packs above); the free GitHub repo is MIT. This post was written with AI assistance, and the hook and config details were checked against the Claude Code, Cursor and Codex docs in October 2026.

Quelle: dev.to (dev.to). Original öffnen