Skip to content

fix(ci): escape the Bun contract's NUL map-key separators - #17959

Merged
lalalune merged 1 commit into
elizaOS:developfrom
ss251:fix/bun-contract-nul-separators
Aug 7, 2026
Merged

fix(ci): escape the Bun contract's NUL map-key separators#17959
lalalune merged 1 commit into
elizaOS:developfrom
ss251:fix/bun-contract-nul-separators

Conversation

@ss251

@ss251 ss251 commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Closes the follow-up @lalalune raised in the post-merge verification of #17599.

Contribution provenance

  • AI assistance: yes
  • Model(s) used: Anthropic/claude-opus-5
  • Client / agent tooling: Claude Code
  • Attribution status: self-reported

What this PR does

packages/scripts/ci-bun-version-contract.mjs built its jobId/stepIndex map key with a raw U+0000 separator typed as a literal control byte instead of its six-character escape. Git classifies any file containing a NUL as binary, so the whole module stopped producing text diffs.

Replaced both with the escape. Behaviour-identical by construction -- the escape and the raw byte are the same character, so the composed keys are indistinguishable and collide in a single Map slot:

escaped === raw: true
codepoint: 0
one map key: true

Why it matters more than a formatting nit

The byte disables exactly the two instruments most likely to catch its neighbours: git diff renders "Binary files differ", and ripgrep skips the file silently. The separator predates the review round on #17599 -- it is present at 22ebc4ec, the head before those fixes -- so every reviewer of that PR was handed no content for this file while reviewing changes to it.

Testing

  • node packages/scripts/ci-bun-version-contract.mjs -- PASS, exit 0, 369 sites scanned, canonical 1.3.14
  • bun test packages/scripts/__tests__/ci-bun-version-contract.test.ts -- 60 pass / 0 fail
  • Targeted Biome -- clean
  • git diff --numstat on the file now reports line counts rather than -, confirming git treats it as text again

Verified against develop@648dddc2 in a worktree branched from it, Bun 1.3.14 / Node 24.15.0.

Evidence gate

  • N/A - build-tooling change with no UI surface
  • N/A - build-tooling change with no UI surface
  • N/A - no user-exercisable flow; a two-character source change in a CI contract script
  • Backend logs: verbatim contract + suite transcript below
verification transcript (verbatim)
$ node packages/scripts/ci-bun-version-contract.mjs
ci bun version contract passed (canonical 1.3.14; 369 sites scanned; 110 workflow pin(s) in lockstep; 9 gate lane(s) pinned)
$ echo $?
0
$ bun test packages/scripts/__tests__/ci-bun-version-contract.test.ts
 60 pass
 0 fail
 96 expect() calls
- [ ] N/A - no frontend code touched - [ ] N/A - no agent, action, prompt, provider or model behaviour touched - [ ] N/A - a two-character source escape in a CI contract script; it produces no persistent domain artifact. The escape/raw-byte equivalence proof is in the backend-logs transcript above.
  • N/A - no rendered views changed

AI provider/model: Anthropic / claude-opus-5
Client / agent tooling: Claude Code
Contribution skill revision: SlopDotCash/slopdotcash@04a50cb:skills/contribute-to-eliza
Attribution status: self-reported
-- [claude-code-ss251]

Follow-up to the post-merge review on elizaOS#17599. The jobId/stepIndex map key
used a raw U+0000 as its separator, typed as a literal control byte rather
than its six-character escape, so git classified the whole file as binary
and stopped producing text diffs for it.

Behaviour-identical by construction -- the escape and the raw byte are the
same character, so the composed keys are indistinguishable and collide in
one Map slot:

    escaped === raw: true
    codepoint: 0
    one map key: true

Verified against develop@648dddc2: contract exits 0 (369 sites), 60/60 in
ci-bun-version-contract.test.ts, Biome clean, and the file diffs as text
again.

Worth recording why this reached develop at all. The separator predates
last week's review work -- it is present at 22ebc4ec, the head before that
round of fixes -- so every reviewer since has been reading a file git
refused to show them. That is precisely the cost of this byte: it disables
the two instruments, diff and grep, most likely to catch its neighbours.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011gZy3feiGVKs61PDUzx1Cd
@lalalune

lalalune commented Aug 7, 2026

Copy link
Copy Markdown
Member

Correct fix, and the bug class is worth naming: ${jobId} ${stepIndex} builds a composite map key with a separator that can legally appear inside the first component. A job id containing a space makes "a b" + " " + "1" and "a" + " " + "b 1" the same key, so envVisible can report a step-level env declaration that belongs to a different job — and this contract's whole job is deciding whether a Bun version pin is visible at a given step, so a false positive there silently passes a workflow that has no pin.

\u0000 is the right separator since it cannot occur in a YAML scalar key. Both the write in analyzeYamlRuntime and the read in envVisible are changed together, which is the part that matters — a half-applied fix would just move the collision.

Gate is red for the queue-wide reason rather than anything here: the fork workflow runs were unapproved (GitHub reports those as status: completed, conclusion: action_required, which the usual ?status=action_required filter misses). Approved and re-triggered.

@ss251

ss251 commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for approving and re-triggering — Security Advisory Gate went green on the re-run, which confirms the red was the hold rather than anything in the diff.

One correction to the record, and it makes your argument stronger rather than weaker.

The shipped separator was already U+0000, not a space. The collision you describe — a job id containing the separator making "a b" + sep + "1" and "a" + sep + "b 1" the same key — could not occur, because the byte in 22ebc4ec really was 0x00, which cannot appear in a YAML scalar key. Verified at both sites:

$ git show 22ebc4ec:packages/scripts/ci-bun-version-contract.mjs | xxd | grep -A0 'bId'
62 49 64 7d 00 24 7b 73 74 65      bId} . ${ste

0x00, not 0x20. So this PR changed the separator's encoding, not its identity — \u0000 and the raw byte are the same character, which is why I ran the equivalence rather than asserting it (escaped === raw: true, one map key: true). There was no live correctness bug; envVisible was never able to read a sibling job's declaration.

And the reason it read as a space in your comment is precisely the defect. A raw NUL renders as whitespace or vanishes in every surface that displays it — GitHub's comment box included — so the byte misrepresented itself in the very message describing it. That is the same mechanism that made git diff say "Binary files differ" and made ripgrep skip the file silently. The problem was never a wrong separator; it was an unreadable one, and it stayed unreadable right up to the point of being discussed.

You're right that the write in analyzeYamlRuntime and the read in envVisible had to move together — a half-applied change would have split one key space into two and made envVisible miss real declarations instead of inventing false ones. Both sites are in the diff for exactly that reason.


Separately, your parenthetical is the most useful thing in this thread for us:

GitHub reports those as status: completed, conclusion: action_required, which the usual ?status=action_required filter misses

We hit that independently a few hours ago and it cost us a false page. Our triage filtered holds on status alone, so nine siblings sitting at completed/action_required counted as zero holds and the aggregate red scored as a genuine code failure. It now reads both fields. Worth knowing that the same lapse shows up on the check-runs API too, where a fork-approval hold never appears at all — so actions/runs is the only surface that carries it, in either of its two spellings.


AI provider/model: Anthropic / claude-opus-5
Client / agent tooling: Claude Code
Contribution skill revision: SlopDotCash/slopdotcash@04a50cb:skills/contribute-to-eliza
Attribution status: self-reported
— [claude-code-ss251]

@lalalune
lalalune merged commit 850216c into elizaOS:develop Aug 7, 2026
35 of 44 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants