Skip to content

fix(start-sdk): drop undefined keys an onWrite transformer produces - #4067

Merged
dr-bonez merged 2 commits into
masterfrom
fix/sdk-filter-undefined-after-onwrite
Sep 23, 2026
Merged

dr-bonez merged 2 commits into
masterfrom
fix/sdk-filter-undefined-after-onwrite

Conversation

@helix-a

@helix-a helix-a commented Sep 23, 2026 •

Copy link
Copy Markdown
Member

What changes

A file model with an onWrite transformer drops the keys that transformer leaves undefined, instead of writing them out as the word undefined.

filterUndefined now runs exactly once per write, on whatever reaches the format's toFile:

  • In the format helpers (rawTransformed), it runs after onWrite, or on the data itself when there is no transformer.
  • In FileHelper.raw, it runs on the data, just before the caller's writer.

The filter is removed from serialize(), which no longer exists: writeFile and merge call writeData directly. rawTransformed builds its FileHelperImpl directly, so the filter is not wrapped twice.

Why

Before #3874, the INI writer filtered in exactly this spot: INI.stringify(filterUndefined(inData)), after any transformer. #3874 moved the filter into serialize() so that the env writer got it too. But serialize() runs before the transformer, so a key the transformer itself sets to undefined reached INI.stringify and was rendered as the word.

Found while testing Bitcoin on SDK 3.0, on a VM built from the #4062 head. Bitcoin's bitcoin.conf model maps its form onto every conf key in onWrite (formToFile). On its first write after an upgrade it wrote 17 such lines, including:

rpcauth=undefined
externalip=undefined
connect=undefined
onlynet=undefined

The next read turned connect=undefined into connect-only to a peer named "undefined", which fails that field's address pattern, and init aborted:

init failed ZodError: [{"code":"custom","path":["connectpeer","value","peers",0], "message":"Must be either a domain name, or an IPv4 or IPv6 address. …"}]

The same round trip on SDK 2.0.9 writes none of those lines.

onWrite now receives the data as given, undefined keys included, as it did before #3874. Bitcoin's formToFile/stringifyPrimitives pass an undefined value through unchanged, so the filter after them drops it.

Changelog

No entry. #3874's entry already describes the intended behaviour under the unreleased 3.0.0, and the INI regression never shipped.

Verification

  • Test: env, ini, yaml and toml each write through an onWrite that sets a key to undefined. Without the fix, the env and ini cases fail.
  • Test: FileHelper.raw hands its writer no key merged as undefined.
  • start-sdk make check and the full jest suite (136 tests) pass. Prettier 3.8.3 passes on both files.

#3874 moved the undefined-key filter from the INI writer into serialize(),
which runs before a file model's onWrite transformer. Keys the transformer
itself leaves undefined then reach the writer, and INI.stringify renders
them as the word: Bitcoin's bitcoin.conf model, whose onWrite maps its form
onto every conf key, wrote `rpcuser=undefined`, `connect=undefined` and 15
more on its first write, and its next read failed validation on
`connect=undefined`. The env writer had the same gap with a transformer
before #3874.

rawTransformed now filters the transformer's output too.

Helix-Harness: claude-code
Helix-Model: claude-opus-5-5
…mat serializer

serialize() filtered the data before any onWrite transformer ran, and the
transformed writer filtered again after it. Each writer now filters exactly
once, on whatever reaches toFile: after onWrite for the format helpers, and
on the data itself for FileHelper.raw. This is where the INI writer filtered
before #3874 moved it up.

Helix-Harness: claude-code
Helix-Model: claude-opus-5-5
@dr-bonez
dr-bonez merged commit 9e5d504 into master Sep 23, 2026
22 of 23 checks passed
@dr-bonez
dr-bonez deleted the fix/sdk-filter-undefined-after-onwrite branch September 23, 2026 21:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants