Skip to content

Commit a8dc2fb

Browse files
fix(ci): the invisible-character gate never matched anything (#51)
**Measured 2026-08-27: this gate caught 0 of 6 invisible-character test cases.** It has never detected an NBSP, zero-width space, BOM, soft hyphen, bidi override or word joiner. ## Root cause The pattern used UTF-8 **byte sequences** (`\xc2\xa0`) while `grep -P` matches **characters**. Bytes `c2 a0` are *one* character U+00A0; `\xc2\xa0` asks for *two*, U+00C2 then U+00A0 — never present. ``` grep -P '\xc2\xa0' -> miss grep -P '\x{a0}' -> MATCH ``` Only `\x00` worked, being single-byte in both readings. The gate ran, passed, and could not see what it exists to see. ## Fixed - **codepoint escapes** in place of byte sequences - **C0 controls** `\x01-\x08,\x0B,\x0C,\x0E-\x1F` added (TAB/LF/CR excluded) - **`grep -a`** — without it grep skips any NUL-bearing file as binary The C0 range matters: a stray **backspace byte** made a workflow unparseable in `developer-ecosystem`, so it never ran — and this linter called it clean. Canonical fix: hyperpolymath/empty-linter#70. **1 file(s)** here. **Verified:** YAML re-parsed, and the corrected pattern was confirmed to catch a real NBSP before the change was kept.
1 parent f4fa5d8 commit a8dc2fb

1 file changed

Lines changed: 30 additions & 2 deletions

File tree

‎.github/workflows/dogfood-gate.yml‎

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ jobs:
119119
# Checks for: zero-width spaces, zero-width joiners, BOM, soft hyphens,
120120
# non-breaking spaces, null bytes, and other invisible Unicode in source files.
121121
set +e
122-
PATTERNS='\xc2\xa0|\xe2\x80\x8b|\xe2\x80\x8c|\xe2\x80\x8d|\xef\xbb\xbf|\xc2\xad|\xe2\x80\x8e|\xe2\x80\x8f|\xe2\x80\xaa|\xe2\x80\xab|\xe2\x80\xac|\xe2\x80\xad|\xe2\x80\xae|\x00'
122+
PATTERNS='\x00|[\x01-\x08\x0B\x0C\x0E-\x1F]|\x{a0}|\x{ad}|\x{200b}|\x{200c}|\x{200d}|\x{200e}|\x{200f}|\x{202a}|\x{202b}|\x{202c}|\x{202d}|\x{202e}|\x{2060}|\x{feff}'
123123
find "$GITHUB_WORKSPACE" \
124124
-not -path '*/.git/*' -not -path '*/node_modules/*' \
125125
-not -path '*/.deno/*' -not -path '*/target/*' \
@@ -130,7 +130,7 @@ jobs:
130130
-o -name '*.yml' -o -name '*.yaml' -o -name '*.md' -o -name '*.adoc' \
131131
-o -name '*.idr' -o -name '*.zig' -o -name '*.v' -o -name '*.jl' \
132132
-o -name '*.gleam' -o -name '*.hs' -o -name '*.ml' -o -name '*.sh' \) \
133-
-exec grep -Prl "$PATTERNS" {} \; > /tmp/empty-lint-results.txt 2>/dev/null
133+
-exec grep -aPrl "$PATTERNS" {} \; > /tmp/empty-lint-results.txt 2>/dev/null
134134
EL_EXIT=$?
135135
set -e
136136
@@ -139,13 +139,41 @@ jobs:
139139
echo "exit_code=$EL_EXIT" >> "$GITHUB_OUTPUT"
140140
echo "ready=true" >> "$GITHUB_OUTPUT"
141141
142+
# Blocking subset: C0 controls and NUL only (owner ruling 2026-08-28).
143+
# Invisible Unicode (NBSP/BOM/zero-width) stays ADVISORY - about 2,100
144+
# estate files carry it as legitimate typography in prose.
145+
blocking=0
146+
while IFS= read -r bf; do
147+
[ -z "$bf" ] && continue
148+
if grep -qaP '\x00|[\x01-\x08\x0B\x0C\x0E-\x1F]' "$bf"; then
149+
blocking=$((blocking+1))
150+
echo "::error file=${bf#$GITHUB_WORKSPACE/}::C0 control characters or NUL bytes - file corruption, blocks the gate"
151+
fi
152+
done < /tmp/empty-lint-results.txt
153+
echo "blocking=$blocking" >> "$GITHUB_OUTPUT"
154+
142155
# Emit annotations for each file with invisible chars
143156
while IFS= read -r filepath; do
144157
[ -z "$filepath" ] && continue
145158
REL_PATH="${filepath#$GITHUB_WORKSPACE/}"
146159
echo "::warning file=${REL_PATH}::Invisible Unicode characters detected (zero-width space, BOM, NBSP, etc.)"
147160
done < /tmp/empty-lint-results.txt
148161
162+
# Enforce (owner ruling 2026-08-28): C0/NUL corruption BLOCKS; other
163+
# invisible Unicode stays advisory. Enforcement lives inside this step
164+
# so a crash above fails the job directly - counts can never arrive
165+
# empty into a separate check that then passes silently.
166+
if [ "$EL_EXIT" -ne 0 ]; then
167+
echo "::warning::invisible-character scan exited $EL_EXIT - results may be incomplete"
168+
fi
169+
if [ "${blocking:-0}" -gt 0 ]; then
170+
echo "## Empty-linter: BLOCKED - $blocking file(s) with C0/NUL corruption" >> "$GITHUB_STEP_SUMMARY"
171+
echo "::error::$blocking file(s) contain C0 control characters or NUL bytes - corruption, not typography. See file annotations."
172+
exit 1
173+
elif [ "${FINDINGS:-0}" -gt 0 ]; then
174+
echo "::notice::$FINDINGS file(s) carry invisible Unicode (NBSP/BOM/zero-width) - advisory only"
175+
fi
176+
149177
- name: Write summary
150178
run: |
151179
if [ "${{ steps.lint.outputs.ready }}" = "true" ]; then

0 commit comments

Comments
 (0)