#1607·adk-go

session: Event.IsFinalResponse panics on a nil *genai.Part in Content.Parts

Author: coderdailyoneCreated Sep 16, 2026Updated Sep 18, 2026
Labelsbug

Describe the bug

genai.Content.Parts is a []*genai.Part, so a nil element is representable in content that comes from outside the library — a null array entry in JSON ("parts":[null]), or content built by a non-genai model backend or a remote agent. All three helpers behind Event.IsFinalResponse dereference each part unchecked and panic with a nil pointer dereference: hasFunctionCalls, hasFunctionResponses, and hasTrailingCodeExecutionResult.

This is the same defect class fixed for the internal/utils accessors in #1556; the session helpers were left unguarded.

Where

  • session/session.go:439part.FunctionCall in hasFunctionCalls
  • session/session.go:451part.FunctionResponse in hasFunctionResponses
  • session/session.go:464lastPart.CodeExecutionResult in hasTrailingCodeExecutionResult

All three are called from Event.IsFinalResponse at session/session.go:230.

Impact

IsFinalResponse runs per event on several hot paths: the base flow's final-response bookkeeping (internal/llminternal/base_flow.go:141), the OutputKey save in agent/llmagent (agent/llmagent/llmagent.go:530), workflow agent nodes (workflow/agent_node.go:220), the console launcher (cmd/launcher/console/console.go:252), and the logging plugin (plugin/loggingplugin/logging_plugin.go:158). One stored or received event carrying a nil part panics the whole process on any of them — including the read path, since a null part persisted to a session backend decodes back into a nil *genai.Part.

How to reproduce

go
func TestIsFinalResponseSkipsNilParts(t *testing.T) {
	var event session.Event
	if err := json.Unmarshal([]byte(
		`{"content":{"role":"model","parts":[null,{"text":"hi"}]}}`),
		&event); err != nil {
		t.Fatalf("json.Unmarshal failed: %v", err)
	}
	_ = event.IsFinalResponse() // panics: nil pointer dereference at session.go:439
}

Suggested fix

Skip nil parts in hasFunctionCalls and hasFunctionResponses (part != nil &&), and nil-check lastPart in hasTrailingCodeExecutionResult. A nil part carries no function call, response, or code execution result, so skipping it preserves the helpers' contract — the same treatment the rest of the codebase already gives nil parts (internal/utils accessors, agent/remoteagent/v2, workflow).