Skip to content

fix(go/ai): make ModelResponse.History nil-safe and stop aliasing request messages - #5883

Open
apascal07 wants to merge 2 commits into
mainfrom
ap/go-history-nil-aliasing
Open

fix(go/ai): make ModelResponse.History nil-safe and stop aliasing request messages#5883
apascal07 wants to merge 2 commits into
mainfrom
ap/go-history-nil-aliasing

Conversation

@apascal07

Copy link
Copy Markdown
Collaborator

Fixes three panic paths and a slice-aliasing defect in ModelResponse.History, found while verifying #4683.

The nil guard dereferenced the receiver it was checking: if mr == nil fell through to return mr.Request.Messages. Request is nil-able independently of the receiver and is nil in shipped code, since the Veo background model stashes its request in Metadata["inputRequest"] and never assigns it, so op.Output.History() panicked on any Veo operation. A response with neither field set panicked back in the guard.

Separately, append(mr.Request.Messages, mr.Message) reused spare capacity in Request.Messages when it had any, writing the response message into a slot the caller still owned. The result aliased anything else pointing into that array, and two calls on one response returned slices sharing the element each had just written.

History now returns early on a nil receiver, treats a nil Request as no prior messages, and builds the result with slices.Clone, which also preserves the old nil return for the empty case.

TestModelResponseHistory covers seven cases: five panic or fail on main, two pin behavior that already worked. Go 1.26.3, full go test ./... in go/: 40 packages pass, 0 fail.

Populating Request on every model path is a plugin-contract change and belongs with the compat_oai streaming gap in #5239.

History dereferenced its receiver inside the guard meant to protect
against a nil receiver, so the nil check guaranteed the panic it was
written to prevent. Request could also be nil independently, since not
every model plugin assigns it, which panicked on the append path.

History also appended the response message directly onto
Request.Messages. When that slice had spare capacity the append wrote
into the caller's backing array, so the result aliased anything else
pointing at it and successive calls overwrote each other.

Return early on a nil receiver, treat a nil Request as no prior
messages, and build the result from a clone so callers own the slice
they get back.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the History() method of ModelResponse to ensure it always returns a freshly allocated slice, preventing callers from accidentally mutating the underlying request messages. It also adds comprehensive unit tests to verify this behavior. The review feedback points out a performance optimization in History() where appending to slices.Clone causes a double allocation, and suggests pre-allocating the slice instead.

Comment thread go/ai/generate.go Outdated
@apascal07
apascal07 requested a review from huangjeff5 August 1, 2026 02:26
@apascal07
apascal07 marked this pull request as ready for review August 1, 2026 02:26
slices.Clone allocates at capacity equal to length for most sizes, so
appending the response message reallocated and recopied. Size the slice
once and copy into it.
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.

1 participant