Force-push guard (any branch)
Force-with-lease only — never clobber a teammate
Blocks bare git push --force / -f on any branch — not just main — and points the agent to --force-with-lease, which refuses to overwrite commits it has not seen. Complements the main/master push guards for shared feature branches.
What does the Force-push guard (any branch) hook do?
Force-push guard (any branch) is a Claude Code PreToolUse hook matching Bash. It fires automatically at that lifecycle event — outside the model, so it can't be skipped or forgotten. Force-with-lease only — never clobber a teammate.
As a PreToolUse hook it runs before the action completes, so it can block or adjust what Claude is about to do. 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
- Shared branch safety
- Team collaboration
- History protection
Tags
settings.json fragment
{
"hooks": {
"PreToolUse": [
{
"hooks": [
{
"command": "node $CLAUDE_PROJECT_DIR/.claude/hooks/guard-force-push-any.mjs",
"type": "command"
}
],
"matcher": "Bash"
}
]
}
}Script · .claude/hooks/guard-force-push-any.mjs
#!/usr/bin/env node
// Bloque git push --force / -f sur toute branche, recommande --force-with-lease (PreToolUse Bash)
import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';
function stripQuotedArgs(cmd) {
return cmd.replace(/"(?:[^"\\]|\\.)*"/g, '""').replace(/'(?:[^'\\]|\\.)*'/g, "''");
}
export function run(input) {
if (input.tool_name && input.tool_name !== 'Bash') return null;
const command = stripQuotedArgs(input.tool_input?.command ?? '');
if (!/\bgit\s+push\b/.test(command)) return null;
// --force-with-lease est le force-push SÛR : on le laisse passer.
const hasLease = /--force-with-lease\b/.test(command);
// --force « nu » ou un flag court combiné contenant f (ex. -fu, -f).
const hasBareForce = /--force\b(?!-with-lease)/.test(command) || /(?:^|\s)-[a-eg-zA-Z]*f[a-zA-Z]*\b/.test(command);
if (hasBareForce && !hasLease) {
return {
decision: 'block',
reason:
'git push --force écrase aveuglément le travail distant. ' +
'Utilisez --force-with-lease : il refuse de clobberer les commits poussés par quelqu\'un d\'autre. ' +
'Si le force-push nu est réellement voulu, lancez-le manuellement.',
};
}
return 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) process.stdout.write(JSON.stringify(result));
}
Learn more
Related hooks
- Secret detection before Bash executionCatch a leaked API key before it ever runs
- Destructive command blockingStops a disk-wiping shell command before it runs
- Sensitive file write protectionYour .env and keys stay untouched by the agent
- Lock file write protectionLock files stay intact - package manager only