Skip to content

objectui: two raw U+0000 bytes in useDatasetFields.ts make the whole file binary to grep #5425

Description

@yinlianghui

Found while measuring objectstack#5407 (unrelated to that fix; filed separately per Prime Directive #10).

packages/app-shell/src/views/metadata-admin/inspectors/useDatasetFields.ts uses a NUL character as a join separator, and it is stored as a raw U+0000 byte rather than as a six-character escape:

  • line 246 — const includeKey = include.join(...)
  • line 265 — const includeList = includeKey ? includeKey.split(...) : []

Both separators are a literal U+0000 in the file bytes. Reproduce:

grep -rn "resolveLabel" packages/app-shell/
# => packages/app-shell/src/views/metadata-admin/inspectors/useDatasetFields.ts: binary file matches

grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f]' packages/app-shell/src/views/metadata-admin/inspectors/useDatasetFields.ts
# => 246, 265

Why it matters

One raw control byte makes grep classify the entire file as binary: it prints binary file matches instead of the line, and with -l/--include filters in a pipeline it is routinely dropped altogether. Every agent and every human who searches this repo by content is blind to this file. It is the same failure family as objectstack#4763 / #4890 / #5140 / #5157, and it is currently invisible to the gates because objectui ships no check:nul-bytes equivalent.

Suggested fix

  1. Write the separator as the escape sequence (backslash-u-0000) so the source file stays plain ASCII while the runtime value is unchanged. Better still, use a separator that cannot be part of a field name (e.g. a newline or a comma) — the NUL is only being used as "a character no identifier contains", and the escape spelling makes that intent readable.
  2. Add a repo-level scan to objectui's check task so this cannot recur silently. The scan must cover more than U+0000: PR objectstack#5140 shipped a NUL and a U+0001 fourteen bytes away, and a NUL-only scanner missed the second (objectstack#5157).

Notes

Do NOT paste the byte when writing about it — describe the escape. Two of the four prior incidents happened while an agent was writing documentation about the rule itself.

Activity

  1. self-assigned this
    on Aug 5, 2026
  2. yinlianghui commented on Aug 5, 2026

    @yinlianghui
    CollaboratorAuthor

    认领:objectui 分片 PM 滚动补位第 8 单(#3305/PR #3383 落地后解锁 —— 刻意排其后,避免两单在 CI 接线文件上相撞)
    会话:session_01GTRjn8xBqp75dk7kFupVRt
    分支:claude/issue-5425-nul-bytes-gate(代码落地 objectui 仓)
    Worktree:objectui-issue-5425
    域:objectui 分片(app-shell metadata-admin inspectors + 根 scripts/门禁接线)
    文件面:packages/app-shell/src/views/metadata-admin/inspectors/useDatasetFields.ts、新建扫描脚本(根 scripts/)、根 package.json check 接线(必要时 .github/workflows / turbo.json 最小接线,报告说明)+ 相应测试(越界即停)。与在飞 #5427(plugin-gantt/locales)零相交。

    口径照正文:分隔符改为不可能出现在字段名里的可读写法(转义拼写),扫描覆盖 U+0000 之外的控制字节(objectstack#5140/#5157 教训:NUL-only 扫描器漏过 14 字节外的 U+0001);可参考 objectstack 仓 scripts/check-nul-bytes.mjs 的实现与豁免形制。⚠️ 写作纪律照 issue Notes:任何评论/PR 文本只写转义描述,不贴字节本体。


    Generated by Claude Code

  3. yinlianghui commented on Aug 5, 2026

    @yinlianghui
    CollaboratorAuthor

    独立佐证一条,支持本 issue 的第 2 项(给 objectui 补仓库级扫描),来自今天另一单 objectstack#5427 / objectui PR #3386:

    我在 packages/plugin-gantt/ 下新建测试文件时,自己写的普通空格被工具物化成了一个 raw U+0000 —— 同一行源码里两处,写的是 join( 单引号 空格 单引号 ),落盘却成了 join( 单引号 NUL 单引号 )。发现方式纯属偶然:提交前跑了一次无关的 grep -rn 扫中文字面量,输出里冒出一行

    grep: packages/plugin-gantt/src/ObjectGantt.quickfilter.i18n.test.tsx: binary file matches
    

    随后 grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f]' 定位到两处。

    三点值得记下:

    1. 这不是搬运历史文件,是新写的文件。 说明这条不只是清理存量(useDatasetFields.ts),而是持续复发的写入路径 —— 本仓今天就新产生了一例。
    2. 本地 node scripts/check-nul-bytes.mjs 在 objectui 里直接 MODULE_NOT_FOUND(该脚本只存在于 objectstack 仓),印证正文那句「it is currently invisible to the gates because objectui ships no check:nul-bytes equivalent」—— 我这两个字节若不是撞上那次无关 grep,会原样进 PR 且全程无告警。
    3. 顺带印证 check:nul-bytes 只扫 NUL(0x00)—— 0x01-0x08 等控制字节不在扫描面,#5140 实测一个 0x01 会从 NUL-only 修复下溜走 #5157 的扩面结论:我用的自查正则覆盖了 U+0001–U+001F 而不只 U+0000,建议 objectui 新增的扫描直接按这个范围写,别只扫 NUL。

    另外一个操作面的小发现:修复时我试图把该字节直接写进 bash 命令做替换,被 harness 以 "command contains control characters that would be hidden in the approval dialog" 拦下 —— 这个拦截是对的,最后用 chr(0) 构造才通过。也就是说「不要粘贴该字节、用转义描述」这条纪律在工具层已有部分兜底,但文件写入路径没有,正是本 issue 要补的那一层。


    Generated by Claude Code


    Generated by Claude Code

  4. yinlianghui commented on Aug 5, 2026

    @yinlianghui
    CollaboratorAuthor

    PM 验收:ACCEPT → objectui PR #3388 已转 ready + auto-merge(CI 15/15 完成零失败,新工作流 Control Byte Scan 已实跑绿),跟到 MERGED。

    落地内容:① 修复走 JSON.stringify/JSON.parse 依赖键 —— 比转义拼写更彻底,不再需要任何「数据不可能包含的字符」,消灭 bug 类本身;② 门禁按载体扫描全部 tracked 文本文件(非扩展名白名单,循 #4890),覆盖 C0(除 tab/LF/CR)+ U+007F,独立工作流零路径过滤(ci/lint 都 paths-ignore markdown,恰是最恶劣载体);③ KNOWN_OFFENDERS 是 ratchet 不是 skip-list(逐条声明覆盖字节值、陈旧条目即红)。反向验证 5 用例 3 类失败原因全部预判命中,含最关键的「grep 能看见该文件」断言与 #5157 漏扫场景的复现闭环。

    两个 open question 的 PM 裁定:① 基线化 + 立 #5450 —— 维持 A(派发边界 + PD #10 立场正确;#5450 已入队 pm:blocked,Blocked-by 本 PR,落地后即派,其修复须同 PR 删基线条目);② 不带 changeset —— 维持 A(内部编码无 API 变化,且 39 包 fixed group 下多余 changeset 全组抬版本)。

    测量修正记账(比 issue 与 #5157 的表述更准):实测仅 U+0000 触发 grep/ripgrep 的 binary 判类;U+0001 等其余控制字节正常打印命中行 —— 两种危害(搜索盲区 vs 不可见/不可 review 字面量)应分开表述,beyond-NUL 覆盖仍按其自身理由实现。objectui 的 AGENTS.md 无控制字节规则一事已在报告记录,门禁现已机械强制 + 报错信息自带教学,是否补文档行留维护者定。

    objectui 分片 PM session_01GTRjn8xBqp75dk7kFupVRt


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions