Skip to content

objectui: four more source files carry raw control bytes — three are invisible to grep, one is a stray U+0001 #5450

Description

@yinlianghui

Found while implementing objectstack#5425 (the repo-wide scan it asks for immediately reported these). Filed separately per Prime Directive #10 — objectstack#5425's dispatch scope is useDatasetFields.ts plus the gate, so these are not fixed there.

Blocked-by: objectstack#5425 — its PR adds the KNOWN_OFFENDERS baseline map in scripts/check-control-bytes.mjs that lists these four. Fixing a file here means deleting its baseline entry in the same PR; the script fails on a stale entry, so the two cannot drift apart.

The four files

⚠️ Written as escape descriptions only, never as the byte itself — see the Notes in objectstack#5425. In this issue "the six-character escape" means backslash, lowercase u, four zeroes.

file line byte used as
packages/core/src/evaluator/listConditional.ts 71 U+0000 composite cache key, (label ?? '') + SEP + source
packages/core/src/utils/record-title.ts 45 U+0000 EMPTY_TOKEN sentinel
packages/fields/src/widgets/PeoplePicker.tsx 320 U+0000 .join(SEP) over person ids
packages/plugin-dashboard/src/DatasetWidget.tsx 89 U+0001 .join(SEP) over row-dimension values

Reproduce (the scan the gate runs):

git ls-files -z | tr '\0' '\n' | while IFS= read -r f; do
  LC_ALL=C grep -qaP '[\x00-\x08\x0b\x0c\x0e-\x1f]' "$f" 2>/dev/null && echo "$f"
done

Two different harms, and the measurement that separates them

The first three carry U+0000 and are invisible to content search today:

grep -n "const" packages/core/src/utils/record-title.ts
# => grep: packages/core/src/utils/record-title.ts: binary file matches

packages/plugin-dashboard/src/DatasetWidget.tsx is different, and worth writing down because objectstack#5425's body predicts otherwise. Measured on GNU grep 3.11 and ripgrep 14, writing one control byte per fixture file and searching for a literal on another line:

  • U+0000 is the only byte that triggers binary classification. grep prints binary file matches; ripgrep prints binary file matches (found NUL byte around offset N).
  • U+0001, U+0002, U+0007, U+0008, U+000B, U+000C, U+000E, U+001A, U+001B, U+001F and U+007F all print the matching line normally in both tools.

So DatasetWidget.tsx is not grep-blind — grep -n "rowDims" on it returns line 67 as normal text. Its defect is the other one: an invisible byte sitting in a string literal, which no reviewer can see, no diff renders, and which is indistinguishable from a typo'd neighbour. That is still a real defect (a separator nobody can read or verify), but it is not the code-search outage the other three are, and the fix priority should reflect that.

This also sharpens objectstack#5157's lesson rather than contradicting it: a NUL-only scanner does miss these, and they should not be in the tree — but the reason to reject them is "never intentional, never reviewable", not "grep goes blind". The gate landing in objectstack#5425 covers both classes and says which harm applies to which byte.

Suggested fix

Same shape as objectstack#5425's: replace each separator with a readable spelling that cannot occur in the joined values — a newline, or the six-character escape named above if the runtime value must stay byte-identical — then delete that file's entry from KNOWN_OFFENDERS. Four one-line changes; record-title.ts's EMPTY_TOKEN is a sentinel rather than a separator, so check that nothing compares it against persisted data before changing its value.

⚠️ Writing discipline: any PR or comment text describes the escape and never pastes the byte. Two of the five prior incidents in this family happened while an agent was writing the rule itself (objectstack#4763 / #4890 / #5140 / #5157). This issue's own first revision lost an escape to that exact trap: a backslash-u escape typed into a JSON tool payload was decoded into a real byte before it ever reached GitHub, then stripped — leaving empty backticks. Spell it in words.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions