fix(deploy): helper polish — pending allowlist, pipefail, no-op guard, --git-dir

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ho5EQCFTSvYamuRtVZpp2d
This commit is contained in:
Bastien Chanot 2026-06-27 17:51:39 +02:00
parent fdc248ded5
commit 0ed074f4bb
2 changed files with 15 additions and 2 deletions

View File

@ -1,9 +1,9 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# deploy-commit.sh — surgical commit for the .claude/deploy/ runbook family. # deploy-commit.sh — surgical commit for the .claude/deploy/ runbook family.
# Allowlist scope = .claude/deploy/ ONLY (inverse of doc-commit's .claude exclusion). # Allowlist scope = .claude/deploy/ ONLY (inverse of doc-commit's .claude exclusion).
set -u set -uo pipefail
_in_git_repo() { git rev-parse --is-inside-work-tree >/dev/null 2>&1; } _in_git_repo() { git rev-parse --git-dir >/dev/null 2>&1; }
_unsafe_state() { # 0 = unsafe _unsafe_state() { # 0 = unsafe
local g; g=$(git rev-parse --git-dir 2>/dev/null) || return 0 local g; g=$(git rev-parse --git-dir 2>/dev/null) || return 0
@ -34,6 +34,13 @@ _in_git_repo || { echo "deploy-commit: not a git repo" >&2; exit 2; }
case "$cmd" in case "$cmd" in
pending) pending)
[ "$#" -gt 0 ] || { echo "deploy-commit: pending needs file args" >&2; exit 2; } [ "$#" -gt 0 ] || { echo "deploy-commit: pending needs file args" >&2; exit 2; }
mapfile -t violations < <(_scope_violations "$@")
if [ "${#violations[@]}" -gt 0 ]; then
{ echo "deploy-commit: REFUSED — path(s) outside .claude/deploy/ allowlist:";
printf ' - %s\n' "${violations[@]}";
echo "deploy-commit: NOTHING committed. Caller must pass only .claude/deploy/ files."; } >&2
exit 4
fi
[ -n "$(_changed_only "$@")" ] && exit 0 || exit 1 ;; [ -n "$(_changed_only "$@")" ] && exit 0 || exit 1 ;;
commit) commit)
msg="${1:-}"; shift || true msg="${1:-}"; shift || true
@ -49,6 +56,9 @@ case "$cmd" in
mapfile -t changed < <(_changed_only "$@") mapfile -t changed < <(_changed_only "$@")
[ "${#changed[@]}" -gt 0 ] || exit 1 [ "${#changed[@]}" -gt 0 ] || exit 1
git add -- "${changed[@]}" git add -- "${changed[@]}"
if git diff --cached --quiet -- "${changed[@]}"; then
echo "deploy-commit: nothing staged — no-op" >&2; exit 1
fi
git commit -q -m "$msg" -- "${changed[@]}" || { echo "deploy-commit: git commit failed" >&2; exit 1; } git commit -q -m "$msg" -- "${changed[@]}" || { echo "deploy-commit: git commit failed" >&2; exit 1; }
git rev-parse --short HEAD ;; git rev-parse --short HEAD ;;
*) echo "usage: deploy-commit.sh pending <file>... | commit \"<msg>\" <file>..." >&2; exit 2 ;; *) echo "usage: deploy-commit.sh pending <file>... | commit \"<msg>\" <file>..." >&2; exit 2 ;;

View File

@ -42,4 +42,7 @@ printf 'i\n' >"$d/.claude/deploy/INCIDENTS.md"; printf '{}\n' >"$d/.claude/deplo
check T7-atomic-rc "$?" 0 check T7-atomic-rc "$?" 0
check T7-three-files "$(git -C "$d" show --name-only --format= HEAD | grep -c deploy)" 3 check T7-three-files "$(git -C "$d" show --name-only --format= HEAD | grep -c deploy)" 3
d=$(mkrepo); printf 'b\n' >"$d/src.txt"
( cd "$d" && bash "$H" pending src.txt ) >/dev/null 2>&1; check T8-pending-out-of-scope-rc "$?" 4
printf 'PASS=%s FAIL=%s\n' "$pass" "$fail"; [ "$fail" -eq 0 ] printf 'PASS=%s FAIL=%s\n' "$pass" "$fail"; [ "$fail" -eq 0 ]