Back to catalogue
ValidationFileChanged· *.ts|*.tsx|*.js|*.jsx
Auto-run tests when source files change
Triggers the project test suite asynchronously whenever a watched source file changes on disk. Results are injected as context for Claude on the next turn, so Claude can react to test failures without being explicitly asked.
Use cases
- Continuously validate that external file changes don't break tests during a session
- Automatically surface test regressions when hot-reloading tools modify source files
- Keep Claude informed of test status after every file system change
Providers & tags
Claude Code
#tests#file-watch#ci#async#validation
settings.json fragment
{
"hooks": {
"FileChanged": [
{
"hooks": [
{
"async": true,
"command": "node $CLAUDE_PROJECT_DIR/.claude/hooks/file-changed-run-tests.mjs",
"timeout": 120,
"type": "command"
}
],
"matcher": "*.ts|*.tsx|*.js|*.jsx"
}
]
}
}Script · .claude/hooks/file-changed-run-tests.mjs
#!/usr/bin/env node
// Relance les tests quand un fichier change (FileChanged)
import { readFileSync } from 'fs';
import { execSync } from 'child_process';
import { fileURLToPath } from 'url';
function defaultExec(cmd) {
return execSync(cmd, { timeout: 90_000 });
}
export function run(input, { exec = defaultExec } = {}) {
if (input.event === 'unlink') return null;
try {
const out = exec('npm test --if-present 2>&1').toString();
return {
hookSpecificOutput: {
hookEventName: 'FileChanged',
additionalContext: `Tests passed after ${input.file_path} changed.\n${out.slice(-500)}`,
},
};
} catch (e) {
const out = (e.stdout ?? e.stderr ?? e.message).toString().slice(0, 1000);
return {
hookSpecificOutput: {
hookEventName: 'FileChanged',
additionalContext: `Tests FAILED after ${input.file_path} changed:\n${out}`,
},
};
}
}
/* 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) process.stdout.write(JSON.stringify(result));
}