Skip to content

Go session events codegen: flat union type causes breaking changes when adding new event types #1031

@SteveSandersonMS

Description

@SteveSandersonMS

Problem

The Go codegen for session events (generated_session_events.go) uses quicktype, which represents the discriminated union of all session event Data payloads as a single flat struct with every property from every event type merged together:

type Data struct {
    Content   *string    // from assistant.message
    Title     *string    // from session.title_changed
    ToolName  *string    // from tool.execution_start
    // ... hundreds of fields from all event types
}

This means that if a new session event type is added that has a property with the same name but a different type as an existing event, the shared field must change type to accommodate both. For example, content was *string but a new event used content as a structured object — quicktype changed it to *DataContent (a union of string and map), breaking all existing consumers that read Content as *string.

This is fundamentally unmanageable: any new session event type can silently break the Go SDK's public API by changing the type of a field that existing consumers rely on.

Impact

  • Every go/generated_session_events.go regeneration is a potential breaking change
  • SDK consumers cannot safely use response.Data.Content (or any shared field) without risking that a future codegen run changes its type
  • We had to revert the session events codegen in PR Add modelCapabilities override to all SDK languages #1029 because it introduced a breaking Content type change unrelated to the PR's purpose

Desired outcome

A representation of session event data that is additive-only — adding a new event type should never change the types of existing event payloads. Some options:

  1. Per-event-type structs: Generate a separate struct for each event type's data (e.g., AssistantMessageData, ToolExecutionStartData), with a typed accessor on SessionEvent that returns the concrete type based on the Type discriminator. This is what the C# codegen does.

  2. Embed + type-switch: Keep a minimal base struct with common fields, and generate per-event structs that consumers access via a type switch or typed getter method.

  3. Interface-based: Define a SessionEventData interface with per-type implementations, using a custom UnmarshalJSON that dispatches based on the type field.

All of these ensure that adding event type B never changes the struct shape of event type A.

Current workaround

In #1029 I explicitly reverted the Go-specific session events codegen change, and added a build check suppression (09f7cee). When we fix the code generator, we must also re-enable the build check.

Metadata

Metadata

Assignees

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions