HookStackGitHub
Back to catalogue
ValidationPostToolUse· Write|EditPostToolUseAfter tool execution · non-blocking· non-blocking

ESLint lint after write

Lint errors fixed in the same loop, not in CI

Runs ESLint on the file just written and returns the errors to the agent so it fixes them immediately within the same loop.

What does the ESLint lint after write hook do?

ESLint 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.

Use cases

  • Continuous quality
  • Auto-fix loop
  • Lint standards

Tags

#validation#eslint#lint#quality

settings.json fragment

{
  "hooks": {
    "PostToolUse": [
      {
        "hooks": [
          {
            "command": "node $CLAUDE_PROJECT_DIR/.claude/hooks/eslint-check.mjs",
            "type": "command"
          }
        ],
        "matcher": "Write|Edit"
      }
    ]
  }
}

Script · .claude/hooks/eslint-check.mjs

#!/usr/bin/env node
// Vérifie le fichier avec ESLint après écriture (PostToolUse Write|Edit)
import { readFileSync } from 'fs';
import { execSync } from 'child_process';
import { fileURLToPath } from '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?$/.test(filePath)) return null;

  try {
    exec(`npx --no-install eslint --max-warnings=0 "${filePath}"`);
    return null;
  } catch (err) {
    const output = err.stdout?.toString() ?? '';
    return output ? { message: `ESLint: ${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);
}