Skip to content

Commit 32567ae

Browse files
authored
ci(hooks): enforce --signoff on git commit (#129)
## Summary - Add a `beforeShellExecution` hook (Cursor) and `PreToolUse` hook (Claude Code) that block `git commit` without `--signoff`/`-s` - Hook returns `agentMessage` so the agent self-corrects automatically - Add DCO sign-off one-liner to AGENTS.md for editors without hook support (Windsurf, etc.) ## Motivation Agent committed with a manually-written `Signed-off-by` that didn't match the git config identity, causing DCO check failure on PR #127. This hook prevents that class of error at the tool level. ## Hook test results | Input | Expected | Result | |-------|----------|--------| | `git commit -m "test"` | Block (exit 2) | exit 2 + agentMessage | | `git commit --signoff -m "test"` | Allow (exit 0) | exit 0 | | `git commit -s -m "test"` | Allow (exit 0) | exit 0 | | `git commit -sm "test"` | Allow (exit 0) | exit 0 | | `git commit --amend --signoff --no-edit` | Allow (exit 0) | exit 0 | | `git commit --amend --no-edit` | Block (exit 2) | exit 2 | | `git commit -a -m "test"` | Block (exit 2) | exit 2 | | `git status` | Allow (exit 0) | exit 0 | | `{}` (no command field) | Allow (exit 0) | exit 0 | ## Test plan - [ ] In Cursor, run `git commit -m "test"` -- should be blocked with agentMessage - [ ] In Cursor, run `git commit -s -m "test"` -- should proceed - [ ] DCO check passes on this PR Signed-off-by: aagonzales <aagonzales@nvidia.com>
1 parent bdd6a08 commit 32567ae

4 files changed

Lines changed: 37 additions & 0 deletions

File tree

.claude/settings.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"hooks": {
3+
"PreToolUse": [
4+
{
5+
"matcher": "Bash",
6+
"hooks": [
7+
{
8+
"type": "command",
9+
"command": ".cursor/hooks/enforce-signoff.sh"
10+
}
11+
]
12+
}
13+
]
14+
}
15+
}

.cursor/hooks.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414
}
1515
],
1616
"beforeShellExecution": [
17+
{
18+
"command": ".cursor/hooks/enforce-signoff.sh",
19+
"matcher": "git commit"
20+
},
1721
{
1822
"command": ".cursor/hooks/audit.sh"
1923
}

.cursor/hooks/enforce-signoff.sh

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/bin/bash
2+
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
# Blocks `git commit` without --signoff/-s. Cursor exit code 2 = deny action.
6+
# The agentMessage tells the agent to retry with --signoff.
7+
8+
json_input=$(cat)
9+
command=$(echo "$json_input" | jq -r '.command // empty')
10+
11+
if echo "$command" | grep -qE '^git commit' && ! echo "$command" | grep -qE '(--signoff| -[a-zA-Z]*s)'; then
12+
echo '{"exitCode": 2, "agentMessage": "All commits require DCO sign-off. Re-run with --signoff (or -s)."}'
13+
exit 2
14+
fi
15+
16+
exit 0

AGENTS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ Common commands: `make test` (unit tests), `make format` (ruff + copyright), `ma
2424

2525
Feature branches off `main`. Branch names often include an issue number prefix (e.g., `<author>/123-short-name`).
2626

27+
All commits require DCO sign-off. Always use `git commit --signoff` (or `-s`) -- never write the `Signed-off-by` trailer manually.
28+
2729
For testing, building, syncing, bootstrapping, and other workflows, see the matching skill or `.claude/commands/` file.
2830

2931
## Module Map

0 commit comments

Comments
 (0)