HookStack
Back to catalogue
ValidationPostToolBatch

Typecheck after parallel file edits

One tsc pass after a batch, not one per file

Runs tsc --noEmit once after a full batch of parallel tool calls that include TypeScript file edits. Avoids redundant per-file tsc invocations and reports type errors back to Claude as context for the next turn.

What does the Typecheck after parallel file edits hook do?

Typecheck after parallel file edits is a Claude Code PostToolBatch hook. It fires automatically at that lifecycle event — outside the model, so it can't be skipped or forgotten. One tsc pass after a batch, not one per file.

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

  • Run a single TypeScript check after Claude edits multiple .ts/.tsx files in parallel
  • Surface type errors immediately after batch refactors or code generation
  • Reduce tsc invocation overhead versus per-file PostToolUse hooks

Tags

#typescript#typecheck#batch#validation

settings.json fragment

{
  "hooks": {
    "PostToolBatch": [
      {
        "hooks": [
          {
            "command": "node $CLAUDE_PROJECT_DIR/.claude/hooks/post-tool-batch-typecheck.mjs",
            "type": "command"
          }
        ]
      }
    ]
  }
}

Script · .claude/hooks/post-tool-batch-typecheck.mjs

#!/usr/bin/env node
// @hookstack post-tool-batch-typecheck
import { execSync } from "node:child_process";
// @hookstack post-tool-batch-typecheck
// Vérifie les types TypeScript après un lot d'écritures (PostToolBatch)
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";

function defaultExec(cmd) {
	return execSync(cmd, { stdio: "pipe", timeout: 30_000 });
}

export function run(input, { exec = defaultExec } = {}) {
	const calls = input.tool_calls ?? [];
	const hasTs = calls.some(
		(c) =>
			["Write", "Edit"].includes(c.tool_name) &&
			/\.tsx?$/.test(c.tool_input?.file_path ?? ""),
	);
	if (!hasTs) return null;

	try {
		exec("npx --no-install tsc --noEmit --pretty false 2>&1");
		return null;
	} catch (e) {
		const out = (e.stdout ?? e.stderr ?? "").toString().slice(0, 2000);
		return {
			hookSpecificOutput: {
				hookEventName: "PostToolBatch",
				additionalContext: `TypeScript errors after batch edit:\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));
}

Learn more

Related hooks