Back to catalogue
ValidationPostToolUse· Write|Edit
Biome lint after write
Lint errors fixed in the same loop, not in CI
Runs Biome on the file just written and returns the errors to the agent so it fixes them immediately within the same loop.
What does the Biome lint after write hook do?
Biome lint after write is a Claude Code PostToolUse hook matching Write|Edit. It fires automatically at that lifecycle event — outside the model, so it can't be skipped or forgotten. Lint errors fixed in the same loop, not in CI.
As a PostToolUse hook it runs after the action, reacting to what just happened rather than blocking it. Because it is a deterministic Node.js script, it executes on every matching event without relying on the model to remember — the guarantee that makes agentic workflows safe to automate.
Use cases
- Continuous quality
- Auto-fix loop
- Lint standards
Tags
#validation#biome#lint#quality
settings.json fragment
{
"hooks": {
"PostToolUse": [
{
"hooks": [
{
"command": "node $CLAUDE_PROJECT_DIR/.claude/hooks/post-write-biome.mjs",
"type": "command"
}
],
"matcher": "Write|Edit"
}
]
}
}Script · .claude/hooks/post-write-biome.mjs
#!/usr/bin/env node
// @hookstack post-write-biome
// Vérifie le fichier avec Biome après écriture (PostToolUse Write|Edit)
import { execSync } from "node:child_process";
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
function defaultExec(cmd) {
return execSync(cmd, { stdio: "pipe", timeout: 15_000 });
}
export function run(input, { exec = defaultExec } = {}) {
const filePath = input.tool_input?.file_path ?? "";
if (!filePath || !/\.([cm]?[jt]sx?|jsonc?)$/.test(filePath)) return null;
try {
// `biome check` (et non `biome lint`) pour matcher le CI qui lance
// `biome check .` (lint + format + organize imports) — sinon divergence.
exec(`npx --no-install biome check --error-on-warnings "${filePath}"`);
return null;
} catch (err) {
const output = err.stdout?.toString() ?? "";
return output ? { message: `Biome: ${output.trim()}\n` } : null;
}
}
/* v8 ignore next 5 */
if (process.argv[1] === fileURLToPath(import.meta.url)) {
const input = JSON.parse(readFileSync(0, "utf8"));
const result = run(input);
if (result?.message) process.stderr.write(result.message);
}