Skip to content

Nil pointer dereference in base_flow.go: runconfig.FromContext returns nil #586

@mohamed-benali

Description

@mohamed-benali

Describe the bug

runconfig.FromContext(ctx) returns nil when RunConfig is not set as a context value, causing a nil pointer dereference in internal/llminternal/base_flow.go:310:

useStream := runconfig.FromContext(ctx).StreamingMode == runconfig.StreamingModeSSE

Root Cause

runconfig.ToContext() is never called anywhere in the ADK codebase. agent.Run() at agent/agent.go:181 copies ctx.RunConfig() into the internal invocationContext.runConfig struct field, but base_flow.go reads from the Go context via runconfig.FromContext(ctx) — not from the struct field. Since nobody puts the RunConfig into the context, FromContext always returns nil.

To Reproduce

Use agent.Run() with a custom InvocationContext implementation (not using the ADK runner). The nil dereference occurs on the first LLM call.

package main

import (
	"context"
	"fmt"

	"google.golang.org/adk/agent"
	"google.golang.org/adk/agent/llmagent"
	"google.golang.org/adk/session"
	"google.golang.org/genai"
)

// Custom InvocationContext (without using runner)
type myInvocationContext struct {
	context.Context
	agent       agent.Agent
	sess        session.Session
	userContent *genai.Content
	ended       bool
}

func (c *myInvocationContext) Agent() agent.Agent          { return c.agent }
func (c *myInvocationContext) Artifacts() agent.Artifacts  { return nil }
func (c *myInvocationContext) Memory() agent.Memory        { return nil }
func (c *myInvocationContext) Session() session.Session    { return c.sess }
func (c *myInvocationContext) InvocationID() string        { return "test" }
func (c *myInvocationContext) Branch() string              { return "main" }
func (c *myInvocationContext) UserContent() *genai.Content { return c.userContent }
func (c *myInvocationContext) RunConfig() *agent.RunConfig {
	return &agent.RunConfig{StreamingMode: agent.StreamingModeNone}
}
func (c *myInvocationContext) EndInvocation()              { c.ended = true }
func (c *myInvocationContext) Ended() bool                 { return c.ended }
func (c *myInvocationContext) WithContext(ctx context.Context) agent.InvocationContext {
	newCtx := *c
	newCtx.Context = ctx
	return &newCtx
}

func main() {
	// ... create agent, session ...
	invCtx := &myInvocationContext{
		Context:     context.Background(),
		agent:       myAgent,
		sess:        sess,
		userContent: genai.NewContentFromText("hello", genai.RoleUser),
	}

	// Panics at base_flow.go:310 — runconfig.FromContext returns nil
	for event, err := range myAgent.Run(invCtx) {
		fmt.Println(event, err)
	}
}

Stack trace

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation]

goroutine N [running]:
google.golang.org/adk/internal/llminternal.(*Flow).runLive(...)
    .../adk/internal/llminternal/base_flow.go:310

Expected behavior

Should not panic. Either guard against nil RunConfig or inject the RunConfig into the context chain via runconfig.ToContext() in agent.Run().

Suggested Fix

Option A — guard nil dereference in base_flow.go:

var useStream bool
if rc := runconfig.FromContext(ctx); rc != nil {
    useStream = rc.StreamingMode == runconfig.StreamingModeSSE
}

Option B — have agent.Run() call runconfig.ToContext() when creating the internal invocation context, so the RunConfig flows through the context chain.

Version

  • ADK v0.5.0 (also present in v0.4.0)
  • Go 1.24

Metadata

Metadata

Labels

bugSomething isn't working

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions