Skill / hook
hooks-plugin/hooks/bash-antipatterns.sh (v2.7.0), the "awk used for file modifications" guard (lines ~301-303).
Category
Bug (false positive)
Description
The guard blocks awk used read-only in a pipeline whenever a downstream command in the same pipeline redirects to a variable path. The intent is to catch awk '...' > file (awk rewriting a file); it instead also fires on awk '...' | sort -u > "$file", where awk only extracts fields and sort does the writing.
Root cause — condition 1's .* is greedy and spans the pipe:
if echo "$COMMAND" | grep -Eq "awk\s+.*>\s*['\"]?[^|]+" && \
echo "$COMMAND" | grep -Eq "(>|>>)\s*['\"]?\\\$"; then
awk\s+.*> matches awk ... | sort -u > because .* crosses the |. The [^|]+ only constrains the char after >, not the span between awk and >.
Evidence
Hit this session on:
chezmoi managed 2>/dev/null | awk -F/ '{print $1}' | sort -u > "$SP/chezmoi-toplevel.txt"
-> blocked with "Use the Edit tool instead of 'awk' for file modifications" (had to rewrite the read-only awk -F/ '{print $1}' as cut -d/ -f1). Two further live demonstrations in the same session: a test-harness command and even the gh issue create for this very report were both blocked, because each merely mentions awk ... > "$..." inside a quoted string literal.
Reproduction against the two conditions:
| Command |
Expected |
Actual |
... | awk -F/ '{print $1}' | sort -u > "$SP/f.txt" (awk read-only) |
pass |
BLOCKED |
awk '{print $1}' data.txt > "$OUT/f.txt" (awk writes) |
BLOCKED |
BLOCKED |
... | awk -F/ '{print $1}' | sort -u (no redirect) |
pass |
pass |
Suggested Action
Constrain condition 1 so a pipe between awk and the redirect prevents the match:
# was: awk\s+.*>\s*['\"]?[^|]+
# use: awk\s+[^|]*>\s*['\"]?[^|]+
if echo "$COMMAND" | grep -Eq "awk\s+[^|]*>\s*['\"]?[^|]+" && \
echo "$COMMAND" | grep -Eq "(>|>>)\s*['\"]?\\\$"; then
awk\s+[^|]*> matches awk '...' > file (true positive) but not awk ... | sort > file (the redirect belongs to a downstream command). Re-running the table above with the fix: row 1 -> pass, row 2 -> BLOCKED, row 3 -> pass.
Note: the same greedy-.*-across-pipes shape may affect sibling checks in the hook worth a quick audit (e.g. the sed/cut chain and commit-file guards).
🤖 Generated with Claude Code
Skill / hook
hooks-plugin/hooks/bash-antipatterns.sh(v2.7.0), the "awk used for file modifications" guard (lines ~301-303).Category
Bug (false positive)
Description
The guard blocks
awkused read-only in a pipeline whenever a downstream command in the same pipeline redirects to a variable path. The intent is to catchawk '...' > file(awk rewriting a file); it instead also fires onawk '...' | sort -u > "$file", whereawkonly extracts fields andsortdoes the writing.Root cause — condition 1's
.*is greedy and spans the pipe:awk\s+.*>matchesawk ... | sort -u >because.*crosses the|. The[^|]+only constrains the char after>, not the span betweenawkand>.Evidence
Hit this session on:
-> blocked with "Use the Edit tool instead of 'awk' for file modifications" (had to rewrite the read-only
awk -F/ '{print $1}'ascut -d/ -f1). Two further live demonstrations in the same session: a test-harness command and even thegh issue createfor this very report were both blocked, because each merely mentionsawk ... > "$..."inside a quoted string literal.Reproduction against the two conditions:
... | awk -F/ '{print $1}' | sort -u > "$SP/f.txt"(awk read-only)awk '{print $1}' data.txt > "$OUT/f.txt"(awk writes)... | awk -F/ '{print $1}' | sort -u(no redirect)Suggested Action
Constrain condition 1 so a pipe between
awkand the redirect prevents the match:awk\s+[^|]*>matchesawk '...' > file(true positive) but notawk ... | sort > file(the redirect belongs to a downstream command). Re-running the table above with the fix: row 1 -> pass, row 2 -> BLOCKED, row 3 -> pass.Note: the same greedy-
.*-across-pipes shape may affect sibling checks in the hook worth a quick audit (e.g. thesed/cutchain and commit-file guards).🤖 Generated with Claude Code