Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 22 additions & 5 deletions dot_claude/hooks/executable_git-staging-guard.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ set -euo pipefail
# 3. Staging files larger than 1MB
# 4. Staging .gitignore changes on non-main/master branches
#
# Exemption: web assets (png/jpg/jpeg/webp/avif/svg/gif + mp4/webm video) under
# public/ in the ambix `web` repo (incl. its `web.*` worktrees) pass the binary
# and size checks up to 3MB — the site has no CDN, media ships from the repo.
#
# Override: set CLAUDE_ALLOW_BULK_ADD=1 to skip bulk-add block for one invocation.

CMD=$(jq -r '.tool_input.command')
Expand Down Expand Up @@ -42,15 +46,24 @@ fi
repo_root=$(git rev-parse --show-toplevel 2>/dev/null || echo "")
cur_branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")

# web/public media exemption (images + mp4/webm): the ambix web repo serves
# media straight from the repo (no CDN). Checked per-file below.
is_web_public_media() {
[[ "$(basename "$repo_root")" == web || "$(basename "$repo_root")" == web.* ]] \
&& [[ "$1" == public/* || "$1" == */public/* ]] \
&& echo "$1" | grep -qiE '\.(png|jpe?g|webp|avif|svg|gif|mp4|webm)$'
}

# 2 + 3 + 4: per-file checks
while IFS= read -r f; do
[[ -z "$f" ]] && continue
# Strip surrounding quotes
f="${f%\"}"; f="${f#\"}"
f="${f%\'}"; f="${f#\'}"

# 2. Binary extension
if echo "$f" | grep -qiE '\.(exe|bin|so|dylib|dll|a|o|dat|db|sqlite|sqlite3|class|jar|war|pyc|wasm|tar|gz|tgz|zip|7z|rar|iso|img|mp4|mov|avi|mkv|pdf)$'; then
# 2. Binary extension (web/public media exempt)
if echo "$f" | grep -qiE '\.(exe|bin|so|dylib|dll|a|o|dat|db|sqlite|sqlite3|class|jar|war|pyc|wasm|tar|gz|tgz|zip|7z|rar|iso|img|mp4|mov|avi|mkv|pdf)$' \
&& ! is_web_public_media "$f"; then
echo "BLOCKED: refusing to stage binary-like file: $f" >&2
echo "If intentional, add via plain git outside this session or add ext to repo .gitattributes." >&2
exit 2
Expand All @@ -62,11 +75,15 @@ while IFS= read -r f; do
full="$repo_root/$f"
fi

# 3. Large file (>1MB)
# 3. Large file (>1MB; 3MB for web/public media — no CDN, media ships from repo)
if [[ -f "$full" ]]; then
size=$(stat -f%z "$full" 2>/dev/null || stat -c%s "$full" 2>/dev/null || echo 0)
if [[ "$size" -gt 1048576 ]]; then
echo "BLOCKED: file >1MB ($((size/1024))KB): $f" >&2
limit=1048576
if is_web_public_media "$f"; then
limit=3145728
fi
if [[ "$size" -gt "$limit" ]]; then
echo "BLOCKED: file >$((limit/1048576))MB ($((size/1024))KB): $f" >&2
echo "Large files rarely belong in source PRs. Use git-lfs or add to .gitignore." >&2
exit 2
fi
Expand Down