HookStack
Back to catalogue
ValidationPostToolUse· Write|Edit

Conflict marker detector

Leftover merge conflict markers caught instantly

After every file write or edit, checks the file on disk for leftover git conflict markers. Requires both an opening and a closing marker line to flag, which avoids false positives on markdown setext underlines. Non-blocking: feeds a warning back to the agent so it resolves the conflict before moving on.

What does the Conflict marker detector hook do?

Conflict marker detector 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. Leftover merge conflict markers caught instantly.

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

  • Catching half-resolved conflicts after an assisted merge or rebase
  • Preventing conflict markers from reaching CI or production
  • Flagging files pasted from a conflicted state

Tags

#validation#git#merge#quality

settings.json fragment

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

Script · .claude/hooks/post-edit-conflict-marker-check.mjs

#!/usr/bin/env node
// @hookstack post-edit-conflict-marker-check
// Signale les marqueurs de conflit git oubliés après une écriture (PostToolUse Write|Edit)
import { existsSync, readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";

const OPEN_MARKER = /^<{7} /m;
const CLOSE_MARKER = /^>{7} /m;

export function run(
	input,
	{ readFile = readFileSync, fileExists = existsSync } = {},
) {
	const filePath = input.tool_input?.file_path ?? "";
	if (!filePath || !fileExists(filePath)) return null;

	let content;
	try {
		content = readFile(filePath, "utf8");
	} catch {
		return null;
	}

	// Les deux bornes sont requises : évite les faux positifs (ex. soulignés markdown)
	if (!OPEN_MARKER.test(content) || !CLOSE_MARKER.test(content)) return null;

	return {
		message: `[conflict-marker] ${filePath} contains git conflict markers (<<<<<<< / >>>>>>>). Resolve the conflict and remove the markers before moving on.\n`,
	};
}

/* 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);
}

Learn more

Related hooks