Skip to content

Consolidate on go.yaml.in/yaml/v3 - #1719

Merged
davidzhao merged 2 commits into
mainfrom
dz/consolidate-yaml
Aug 16, 2026
Merged

Consolidate on go.yaml.in/yaml/v3#1719
davidzhao merged 2 commits into
mainfrom
dz/consolidate-yaml

Conversation

@davidzhao

Copy link
Copy Markdown
Member

Why

protocol compiles two copies of the same yaml library:

module pulled in by
gopkg.in/yaml.v3 auth/provider.go, utils/configutil/observer.go, livekit/types.go
go.yaml.in/yaml/v3 transitively, via buf.build/go/protoyaml

go.yaml.in/yaml is the project's new canonical home for the same codebase, so this switches our own imports over and leaves one copy in the build.

The part that needed care

RoomConfiguration, RoomEgress and RoomAgent implement a custom UnmarshalYAML(value *yaml.Node). That's an interface, and a decoder only recognizes it when the Node parameter is its own type.

Just re-pointing the import would have been a silent, runtime-only break. Every one of these still decodes with gopkg.in/yaml.v3:

livekit/pkg/config/config.go:231
  RoomConfigurations map[string]*livekit.RoomConfiguration `yaml:"room_configurations,omitempty"`

…plus egress, ingress, sip and cloud. Their decoders would stop seeing an Unmarshaler and fall back to reflective struct decoding of a protobuf message — no compile error anywhere, room_configurations fields such as min_playout_delay just quietly come back zero.

What this does instead

The three methods now use the older func-based unmarshal signature, which both packages honor because it names no yaml types at all:

// gopkg.in/yaml.v3@v3.0.1/yaml.go:40  and  go.yaml.in/yaml/v3@v3.0.4/yaml.go:39
type obsoleteUnmarshaler interface {
	UnmarshalYAML(unmarshal func(interface{}) error) error
}

So downstream repos keep working unchanged, whichever yaml package they import — no coordinated migration needed. MarshalYAML() (interface{}, error) was already yaml-package-neutral and is untouched.

Verification

  • TestUnmarshallRoomConfigurationBothYAMLPackages decodes the same document with both packages and asserts a fully populated message.
  • Confirmed the test has teeth: restoring the *yaml.Node signature makes the gopkg.in/yaml.v3 subtest fail with expected: 0x2a, actual: 0x0 on min_playout_delay — precisely the silent-misparse mode above.
  • Full go test ./... passes.
  • End-to-end against the real consumer: built livekit-server's pkg/config against this branch and parsed a config containing room.room_configurations through config.NewConfig. All fields (min_playout_delay: 42, nested egress.room.room_name, agents[]) parse correctly while the server still imports gopkg.in/yaml.v3.
  • Confirmed the payoff downstream: with this branch replace'd into server-sdk-go, its compiled dependency set drops to just go.yaml.in/yaml/v3 + protoyaml; gopkg.in/yaml.v3 is gone.

Note

gopkg.in/yaml.v3 stays in go.mod — the regression test imports it deliberately, since testing the both-decoders property requires both decoders. It is test-only and no longer in the compiled dependency set, so consumers don't build it.

🤖 Generated with Claude Code

protocol pulled in two copies of the same yaml library: gopkg.in/yaml.v3
(auth, configutil, livekit/types) and go.yaml.in/yaml/v3 (transitively via
buf.build/go/protoyaml). go.yaml.in is the project's new canonical home, so
switch our own imports over and compile it once.

RoomConfiguration, RoomEgress and RoomAgent needed care. Their custom
UnmarshalYAML took a *yaml.Node, and a yaml decoder only recognizes that
method if the Node belongs to its own package. Simply re-pointing the import
would mean every caller still decoding with gopkg.in/yaml.v3 -- the server,
egress, ingress, sip and cloud all do -- silently stops invoking the custom
unmarshaller and falls back to reflective struct decoding of a protobuf
message. No compile error; fields such as min_playout_delay just quietly
come back zero.

Instead these now use the func-based unmarshal signature, which names no
types from any yaml package and so is honored by both decoders. Downstream
repos keep working unchanged, whichever yaml package they import.

Added TestUnmarshallRoomConfigurationBothYAMLPackages, which decodes the
same document with both packages and asserts a populated message. Verified
it fails (min_playout_delay 0 instead of 42) if the *yaml.Node signature is
restored. gopkg.in/yaml.v3 stays in go.mod for that test only; it is no
longer in the compiled dependency set.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Aug 16, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: a192275

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@davidzhao
davidzhao merged commit 32681c6 into main Aug 16, 2026
8 checks passed
@davidzhao
davidzhao deleted the dz/consolidate-yaml branch August 16, 2026 15:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants