Skip to content

lint: detect hidden shell producer failures #81

Description

@junior-ricon

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions