Problem
Shell safety flags such as set -euo pipefail do not propagate every producer failure.
A common dangerous shape is a loop fed by process substitution:
while IFS= read -r -d '' path; do
paths+=("$path")
done < <(git diff --cached --name-only -z)
If git diff fails, the parent shell sees the loop's status rather than the producer's status. The loop can succeed with an empty array and let a safety check fail open.
KnickKnackLabs/notes#188 repaired three concrete cases in pre-commit hooks: staged-path enumeration for obfuscation, staged-path enumeration for encryption, and cached attribute inspection. All three hooks previously returned success when the Git producer failed.
Related status-masking shapes include declaration assignments such as local value=$(command), broad error suppression, and consumers that map every nonzero status to an ordinary empty or clean state. Some are already covered by ShellCheck; semantic status collapse is not always statically decidable.
Desired investigation
Design one or more Codebase lint rules that catch reliably detectable hidden producer failures without pretending to solve every fail-open semantic bug.
The investigation should:
- inventory existing ShellCheck coverage before adding overlapping rules;
- detect unobserved process-substitution producer status where practical;
- consider other statically reliable status-masking forms separately;
- define a narrow suppression mechanism for intentional cases;
- prefer syntax-aware analysis if a regex rule would create misleading false confidence; and
- document important fail-open patterns that remain outside static detection.
A safe replacement for the example above snapshots and checks the producer before consuming it:
snapshot=$(mktemp)
if ! git diff --cached --name-only -z > "$snapshot"; then
printf '%s\n' "error: failed to inspect staged paths" >&2
exit 1
fi
while IFS= read -r -d '' path; do
paths+=("$path")
done < "$snapshot"
Acceptance boundary
- Unsafe fixtures fail for each implemented rule.
- Equivalent explicitly checked producer boundaries pass.
- Intentional suppressions are local and visible.
- Tests cover macOS Bash 3.2-compatible and Linux shell source shapes.
- Documentation states what the lint can and cannot prove.
This issue is for lint design and implementation. It does not require rewriting existing repositories as part of the first change.
Problem
Shell safety flags such as
set -euo pipefaildo not propagate every producer failure.A common dangerous shape is a loop fed by process substitution:
If
git difffails, the parent shell sees the loop's status rather than the producer's status. The loop can succeed with an empty array and let a safety check fail open.KnickKnackLabs/notes#188 repaired three concrete cases in pre-commit hooks: staged-path enumeration for obfuscation, staged-path enumeration for encryption, and cached attribute inspection. All three hooks previously returned success when the Git producer failed.
Related status-masking shapes include declaration assignments such as
local value=$(command), broad error suppression, and consumers that map every nonzero status to an ordinary empty or clean state. Some are already covered by ShellCheck; semantic status collapse is not always statically decidable.Desired investigation
Design one or more Codebase lint rules that catch reliably detectable hidden producer failures without pretending to solve every fail-open semantic bug.
The investigation should:
A safe replacement for the example above snapshots and checks the producer before consuming it:
Acceptance boundary
This issue is for lint design and implementation. It does not require rewriting existing repositories as part of the first change.