ai newsMi., 4. Nov.
Eilmeldung(g+) Wirtschaft: AWS-Manager warnt vor Denkfehler beim KI-Einsatz

Claude Code Hooks for Laravel: Don't Let the Agent Say "Done" Until the Checks Pass

A rule in CLAUDE.md is a request. A hook is a gate. Here is a reproduction from my Laravel test app. One route gets a new name, the way an agent renames things while "cleaning up": Route :: get ( '/contact' , [ ContactController :: class , 'create' ]) -> name ( 'contact.create' ); // was contact.fo

dev.toMittwoch 16:30 · 04.11.3 Min. Lesezeit
Bild: dev.to

A rule in CLAUDE.md is a request. A hook is a gate.

Here is a reproduction from my Laravel test app. One route gets a new name, the way an agent renames things while "cleaning up":

Route:: get ( '/contact', [ ContactController:: class, 'create' ]) -> name ( 'contact.create' ); // was contact.form Enter fullscreen mode Exit fullscreen mode The controller still redirects to contact.form, and four lines in a feature test still request it. php -l is happy, the route file looks fine, and nothing complains until someone submits the contact form or runs that one feature test. If the agent doesn't, it reports the task as done.

That is how architecture drifts in a codebase with an AI agent in it: not in one big rewrite, but one renamed route, one skipped check and one "done" at a time. You can write "always run the tests" into CLAUDE.md. Most of the time the agent will. The times it doesn't are the ones that reach production.

Claude Code runs hooks at fixed points of a session, configured in.claude/settings.json. The Stop hook runs when the agent has finished responding and is about to hand the turn back to you. Three details from the docs matter for a gate:

{ "hooks": { "Stop": [ { "hooks": [{ "type": "command", "command": "php artisan test" }] } ] } } Enter fullscreen mode Exit fullscreen mode When a test fails, php artisan test exits with 1. By the rules above, that is a non-blocking error, and the agent finishes anyway. The gate is open.

php artisan test || { echo "Tests fail. Fix them before finishing." > &2; exit 2; } Enter fullscreen mode Exit fullscreen mode It still has four problems:

This is what I built into laravel-ai-verify, the Laravel port of the agent tooling from Semitexa, the PHP framework I'm working on. One command installs it:

composer require --dev semitexa/laravel-ai-verify php artisan ai:verify:install --hook Enter fullscreen mode Exit fullscreen mode It merges a single entry into.claude/settings.json, next to whatever hooks you already have:

{ "hooks": { "Stop": [ { "hooks": [ { "type": "command", "command": "php \" ${CLAUDE_PROJECT_DIR}/artisan \" ai:verify:hook", "timeout": 600 } ] } ] } } Enter fullscreen mode Exit fullscreen mode When Claude is about to finish, the hook runs php artisan ai:verify on the uncommitted work. That command looks at which files changed and plans only the checks they need: the tests that reach the changed code, Larastan on the changed lines, your project's own style tool (Pint, PHP-CS-Fixer, PHP_CodeSniffer or ECS, whichever has a config), a fresh route:list, config:cache or event:list for the kind of file you touched, and migrations in pretend mode. A changed test file must keep its tests and assertions, so deleting the failing test fails too.

For the renamed route, three checks ran in under a second, and the hook answered with a block. This is the reason Claude received, trimmed:

✗ artisan:routes: 5 reference(s) to undefined route names › app/Http/Controllers/ContactController.php:27 Route [contact.form] is not defined (The route was renamed or removed. Update this reference, or restore the name with ->name('contact.form').) › tests/Feature/ContactFormTest.php:14 Route [contact.form] is not defined › tests/Feature/ContactFormTest.php:30 Route [contact.form] is not defined... Re-run php artisan ai:verify after fixing. Do not delete or weaken tests to make it pass. Enter fullscreen mode Exit fullscreen mode File, line and the fix, which is what an agent needs to act on it without asking you.

After a block it checks again every time the agent changes something, so a half-done fix (the controller updated, the tests still calling the old name) is sent back too. Two things keep it from being annoying. It skips a working tree it has already judged, so answering a question in a dirty repo, or stopping again without changing anything, does not re-run the checks. And it gives up after five rounds in one continuation, below Claude Code's own cap of eight.

Which checks do you make your agent pass before it may say "done"?

Source and docs: https://github.com/semitexa/laravel-ai-verify. The ideas come from Semitexa: https://semitexa.com/?utm_source=devto&utm_medium=post&utm_campaign=claude-code-hooks-laravel

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