Skip to content

feat(hooks): require human approval before git push - #81

Merged
DevSecNinja merged 1 commit into
mainfrom
feat/git-push-approval-hook
Jul 27, 2026
Merged

feat(hooks): require human approval before git push#81
DevSecNinja merged 1 commit into
mainfrom
feat/git-push-approval-hook

Conversation

@DevSecNinja

Copy link
Copy Markdown
Owner

What

Adds a Copilot preToolUse command hook that forces an explicit, interactive human approval before the agent runs any git push.

  • .apm/hooks/git-push-approval.json — the hook definition (uses the bash command key, like tool-guardian).
  • scripts/git-push-approval/require-push-approval.sh — the guard script.

Why here (why git push?)

A local commit is fully rewritable — git commit --amend, git reset, git rebase all work freely until it's pushed. git push is the first irreversible step that publishes work to a remote, so it's the correct chokepoint for a human checkpoint.

How it works

  • Reads the tool invocation as JSON on stdin (toolName/toolArgs, and the VS Code tool_name/tool_input variant).
  • When a shell tool (bash/powershell/shell) contains a git … push, it emits {"permissionDecision":"ask", ...} so the CLI prompts the user to approve. Every other tool call produces no decision and flows through the normal permission path.
  • Always exits 0. preToolUse command hooks are fail-closed (a non-zero exit denies the call), so the script only ever upgrades the decision to ask and never blocks unrelated tools.
  • Detection is scoped to a single command (separators ; & | break the match), so git status && npm run push does not prompt; git push, git push origin main, git -C /repo push, and /usr/bin/git --no-pager push do.
  • jq is used when present with a pure-grep/sed fallback when it isn't.

Design choice: enabled by default

Unlike tool-guardian (a heavy blocker shipped with disableAllHooks), this hook ships enabled — an approval prompt is low-friction and being active is the entire point. Disable per session with SKIP_PUSH_APPROVAL=true.

Validation

  • shellcheck (repo's strict .shellcheckrc) and shfmt (per .editorconfig): clean.
  • tests/validate-prompts.sh and scripts/generate-index.sh: pass, no index drift (hooks aren't indexed).
  • Functional matrix (13 cases): push variants → ask; commits, separators, legit push, non-shell edits, and the disable switch → allow. Includes a no-jq fallback run.

Ref: Copilot hooks reference.

Add a Copilot preToolUse command hook that forces an explicit, interactive
approval before the agent runs any `git push`. Local commits stay fully
rewritable (amend/reset/rebase) until pushed, so `git push` is the first
irreversible step to a remote — the right place for a human checkpoint.

The hook reads the tool invocation on stdin and, when a shell tool contains
a `git push`, emits `permissionDecision: "ask"` so the CLI prompts the user;
all other tool calls fall through untouched. It always exits 0 because
preToolUse command hooks are fail-closed (a non-zero exit would deny the
call). Ships enabled; disable with SKIP_PUSH_APPROVAL=true.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@DevSecNinja
DevSecNinja merged commit 4c3a9ab into main Jul 27, 2026
16 checks passed
@DevSecNinja
DevSecNinja deleted the feat/git-push-approval-hook branch July 27, 2026 10:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant