Skip to content

Commit 21c7a77

Browse files
WomB0ComB0claude
andcommitted
fix(git-hooks): use here-strings to avoid echo + grep/sed pitfalls
Replace `echo "$X" | grep ...` with `grep ... <<<"$X"` across all canonical hooks. When `$X` begins with `-`, `echo` can interpret it as a flag and grep/sed can match it as an option, breaking the script. Here-strings pass bytes directly on stdin and are immune to that class of bug. Also fix pre-push stdin consumption: `while read` was draining the ref stream git passes on stdin, leaving the `local-pre-push` hook with nothing. The script now captures stdin once and re-feeds it to both the force-push guard and the local hook. Flagged by gemini-code-assist on resq-software/crates#46 and resq-software/npm#30. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent bb6999b commit 21c7a77

File tree

5 files changed

+29
-17
lines changed

5 files changed

+29
-17
lines changed

scripts/git-hooks/commit-msg

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ INPUT_FILE="${1:-}"
1313
PATTERN="^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?(!)?: .+$"
1414

1515
FIRST_LINE=$(head -1 "$INPUT_FILE")
16-
SUBJECT=$(echo "$FIRST_LINE" | sed 's/^\[[A-Z][A-Z]*-[0-9]*\] //')
16+
SUBJECT=$(sed 's/^\[[A-Z][A-Z]*-[0-9]*\][[:space:]]*//' <<<"$FIRST_LINE")
1717

18-
if ! echo "$SUBJECT" | grep -qE "$PATTERN"; then
18+
if ! grep -qE "$PATTERN" <<<"$SUBJECT"; then
1919
echo "Error: Invalid commit message format."
2020
echo "Expected: type(scope): subject"
2121
echo "Examples:"
@@ -27,7 +27,7 @@ fi
2727
BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || echo "")
2828
case "$BRANCH" in
2929
main|master)
30-
if echo "$FIRST_LINE" | grep -qiE "^(\[[A-Z]+-[0-9]+\] )?(fixup!|squash!|wip[: ])"; then
30+
if grep -qiE "^(\[[A-Z]+-[0-9]+\] )?(fixup!|squash!|wip[: ])" <<<"$FIRST_LINE"; then
3131
echo "Error: fixup!/squash!/WIP commits are not allowed on $BRANCH."
3232
echo "Create a feature branch instead."
3333
exit 1

scripts/git-hooks/post-checkout

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ IS_BRANCH_CHECKOUT="${3:-}"
1616
if [ "$IS_BRANCH_CHECKOUT" = "1" ] && [ "$PREV_HEAD" != "$NEW_HEAD" ]; then
1717
CHANGED=$(git diff --name-only "$PREV_HEAD" "$NEW_HEAD" 2>/dev/null || true)
1818

19-
echo "$CHANGED" | grep -q "^Cargo\.lock$" && echo "📦 Cargo.lock changed — run: cargo build"
20-
echo "$CHANGED" | grep -q "^bun\.lockb\?$" && echo "📦 bun.lock changed — run: bun install"
21-
echo "$CHANGED" | grep -q "^uv\.lock$" && echo "📦 uv.lock changed — run: uv sync"
22-
echo "$CHANGED" | grep -q "^flake\.lock$" && echo "📦 flake.lock changed — exit and re-enter: nix develop"
19+
grep -q "^Cargo\.lock$" <<<"$CHANGED" && echo "📦 Cargo.lock changed — run: cargo build"
20+
grep -q "^bun\.lockb\?$" <<<"$CHANGED" && echo "📦 bun.lock changed — run: bun install"
21+
grep -q "^uv\.lock$" <<<"$CHANGED" && echo "📦 uv.lock changed — run: uv sync"
22+
grep -q "^flake\.lock$" <<<"$CHANGED" && echo "📦 flake.lock changed — exit and re-enter: nix develop"
2323
fi
2424

2525
LOCAL_HOOK="$(git rev-parse --show-toplevel)/.git-hooks/local-post-checkout"

scripts/git-hooks/post-merge

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ set -euo pipefail
1111

1212
CHANGED=$(git diff-tree -r --name-only --no-commit-id ORIG_HEAD HEAD 2>/dev/null || true)
1313

14-
echo "$CHANGED" | grep -q "^Cargo\.lock$" && echo "📦 Cargo.lock changed after merge — run: cargo build"
15-
echo "$CHANGED" | grep -q "^bun\.lockb\?$" && echo "📦 bun.lock changed after merge — run: bun install"
16-
echo "$CHANGED" | grep -q "^uv\.lock$" && echo "📦 uv.lock changed after merge — run: uv sync"
17-
echo "$CHANGED" | grep -q "^flake\.lock$" && echo "📦 flake.lock changed after merge — exit and re-enter: nix develop"
14+
grep -q "^Cargo\.lock$" <<<"$CHANGED" && echo "📦 Cargo.lock changed after merge — run: cargo build"
15+
grep -q "^bun\.lockb\?$" <<<"$CHANGED" && echo "📦 bun.lock changed after merge — run: bun install"
16+
grep -q "^uv\.lock$" <<<"$CHANGED" && echo "📦 uv.lock changed after merge — run: uv sync"
17+
grep -q "^flake\.lock$" <<<"$CHANGED" && echo "📦 flake.lock changed after merge — exit and re-enter: nix develop"
1818

1919
LOCAL_HOOK="$(git rev-parse --show-toplevel)/.git-hooks/local-post-merge"
2020
if [ -x "$LOCAL_HOOK" ]; then

scripts/git-hooks/pre-push

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@ echo "🚀 Running pre-push checks..."
1515

1616
BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || echo "")
1717

18+
# Capture stdin (the ref info git passes to pre-push) so both the force-push
19+
# guard and any local-pre-push hook can read it.
20+
PUSH_REFS=""
21+
if [ ! -t 0 ]; then
22+
PUSH_REFS=$(cat)
23+
fi
24+
1825
# ── Force-push guard on main/master ─────────────────────────────────────────
1926
case "$BRANCH" in
2027
main|master)
@@ -27,7 +34,9 @@ case "$BRANCH" in
2734
exit 1
2835
fi
2936
fi
30-
done
37+
done <<EOF
38+
$PUSH_REFS
39+
EOF
3140
;;
3241
esac
3342

@@ -36,8 +45,8 @@ ALLOWED_PATTERN="^(feat|fix|docs|chore|refactor|test|ci|perf|release)/.*$"
3645
SKIP_BRANCHES="^(main|master|dev|develop|staging|production|changeset-release/.*)$"
3746

3847
if [ -n "$BRANCH" ]; then
39-
if ! echo "$BRANCH" | grep -qE "$SKIP_BRANCHES"; then
40-
if ! echo "$BRANCH" | grep -qE "$ALLOWED_PATTERN"; then
48+
if ! grep -qE "$SKIP_BRANCHES" <<<"$BRANCH"; then
49+
if ! grep -qE "$ALLOWED_PATTERN" <<<"$BRANCH"; then
4150
echo "❌ Branch '$BRANCH' does not follow naming convention."
4251
echo " Expected: type/description (e.g. feat/add-login)"
4352
echo " Allowed prefixes: feat, fix, docs, chore, refactor, test, ci, perf, release"
@@ -50,7 +59,10 @@ fi
5059
# ── Local override (language-specific checks) ───────────────────────────────
5160
LOCAL_HOOK="$(git rev-parse --show-toplevel)/.git-hooks/local-pre-push"
5261
if [ -x "$LOCAL_HOOK" ]; then
53-
"$LOCAL_HOOK" "$@"
62+
# Re-supply captured stdin so local hooks that inspect refs still work.
63+
"$LOCAL_HOOK" "$@" <<EOF
64+
$PUSH_REFS
65+
EOF
5466
fi
5567

5668
echo "✅ Pre-push checks passed"

scripts/git-hooks/prepare-commit-msg

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ esac
1717
BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || echo "")
1818
[ -z "$BRANCH" ] && exit 0
1919

20-
TICKET=$(echo "$BRANCH" | grep -oE '[A-Z]{2,}-[0-9]+' | head -1 || true)
20+
TICKET=$(grep -oE '[A-Z]{2,}-[0-9]+' <<<"$BRANCH" | head -1 || true)
2121
if [ -n "$TICKET" ]; then
2222
COMMIT_MSG=$(cat "$COMMIT_MSG_FILE")
23-
if ! echo "$COMMIT_MSG" | grep -qF "$TICKET"; then
23+
if ! grep -qF -- "$TICKET" <<<"$COMMIT_MSG"; then
2424
printf '[%s] %s\n' "$TICKET" "$COMMIT_MSG" > "$COMMIT_MSG_FILE"
2525
fi
2626
fi

0 commit comments

Comments
 (0)