fix(requestflag): ignore a leading UTF-8 BOM in YAML/JSON input - #199
Open
thegoodengineer wants to merge 1 commit into
Open
thegoodengineer wants to merge 1 commit into
thegoodengineer wants to merge 1 commit into
Conversation
Windows tools commonly start UTF-8 files with a byte order mark; PowerShell 5.1's Set-Content -Encoding UTF8 is one example. go-yaml decodes that BOM as content, so a request file piped to the CLI stopped being an object: - A JSON body decoded as one string and was sent to the API as a quoted JSON string, with exit status 0. - Adding any body flag failed with "Cannot merge flags with a body that is not a map", followed by what is plainly a map. - Path, query, and header values in the file were ignored, for example "Required flag "response-id" not set" from responses retrieve. - A YAML body kept the BOM on its first key, so "model" was sent as "\ufeffmodel". YAML allows a BOM at the start of a stream, and RFC 8259 lets JSON parsers ignore one. Strip a single leading UTF-8 BOM in UnmarshalYAMLOrJSON, which both stdin and JSON flag values go through. A U+FEFF anywhere else is still content, and nothing else about decoding changes. Upstream: goccy/go-yaml#906
thegoodengineer
force-pushed
the
fix/stdin-utf8-bom
branch
from
September 18, 2026 12:43
18d2ea1 to
9e743cf
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A request file that starts with a UTF-8 byte order mark is not read as an object when it is piped to the CLI. Windows tools write this BOM routinely, for example
Set-Content -Encoding UTF8in Windows PowerShell 5.1. The file looks correct in an editor, and the CLI either rejects it with a misleading error or sends the wrong request.Problem
Piped stdin and JSON flag values are decoded by
requestflag.UnmarshalYAMLOrJSON, which passes the bytes to go-yaml. go-yaml treats a leading BOM as content (goccy/go-yaml#906, still open). A JSON document therefore decodes as a single string, and a YAML document keeps the BOM attached to its first key.These inputs were all written by Windows PowerShell 5.1 with
Set-Content -Encoding UTF8(each file starts withef bb bf) and piped with<. Requests were captured with a local echo server, comparingmainwith this branch:mainresponses create < body.json"\ufeff{\"model\":\"gpt-test\",...}", and exits 0{"input":"hi","model":"gpt-test"}responses create --instructions "be brief" < body.jsonCannot merge flags with a body that is not a map: {"model":"gpt-test","input":"hi"}responses create < body.yaml"\ufeffmodel":"gpt-test", so the request has nomodel"model":"gpt-test"responses retrieve < get.jsoncontaining{"response_id":"resp_123"}Required flag "response-id" not set/responses/resp_123Because the BOM is invisible, the last three are hard to diagnose: the error prints a map while saying it is not a map, and the "missing" flag is right there in the file.
Fix
Strip one leading UTF-8 BOM in
UnmarshalYAMLOrJSONbefore decoding. YAML allows a BOM at the start of a stream, and RFC 8259 section 8.1 lets JSON parsers ignore one. Both decode sites have gone through this helper since #195, so stdin and JSON flag values are both covered.Scope:
@filecontents, file uploads, and binary bodies read from stdin are untouched.>orOut-Fileby default, is not handled here. That needs transcoding rather than trimming, and RFC 8259 requires JSON to be UTF-8, so I left it for a separate change.Tests
internal/requestflag/unmarshal_test.gocovers a BOM before a JSON object, a JSON array, a YAML mapping (the first key stays intact), and JSON containing an exponent number, so the two workarounds are shown to compose. It also covers a map flag value, and includes a guard that U+FEFF inside a string value is preserved.cmd/openai/main_stdinbom_test.goruns the real entrypoint with BOM-prefixed stdin in three cases: a JSON body merged with--instructions, a YAML body, and a JSON path parameter forresponses retrieve. It checks the request path and body the server receives, using therunMainDispatchWithStdinhelper added in fix(requestflag): preserve JSON numbers written in exponent form #195.With the one-line fix disabled, all three end-to-end cases fail with the symptoms above:
Every BOM unit case fails the same way, apart from the in-value guard, which passes either way.
Validation
Linux (WSL Ubuntu 22.04) with Go 1.25.14, on a clean clone of this commit, all passing:
go mod verifyandgo mod tidy -diffgo vet ./..../scripts/lintgo test ./... -run '^$'go test ./internal/...andgo test ./cmd/openaigo test ./pkg/cmd -run 'Stdin|EmbedFiles|IsUTF8|Multipart', the existing piped stdin, file embedding, and multipart testsgo test -race ./internal/requestflag ./cmd/openaigo test -race -count=100 -shuffle=on ./internal/requestflag -run 'TestUnmarshalYAMLOrJSON|TestJSONExponentNumbersInFlagValues'go test -race -count=50 ./cmd/openai -run '^(TestMainStdinByteOrderMark|TestMainJSONExponentNumbers)$'gofmt -lon the changed filesOn the same clone, with
internal/requestflag/unmarshal.gorestored frommain, the new end-to-end test fails in all three cases as shown above.I did not run the full
./scripts/testsuite against the Steady mock server; that is left to CI. The before and after table above was captured on Windows with binaries built frommainand from this branch.