charmbracelet/bubbletea · error

bubbletea: InitialModel cannot be nil

Error message

bubbletea: InitialModel cannot be nil

What it means

Program.Run returns this error immediately when the model passed to tea.NewProgram was nil. Bubble Tea requires a non-nil Model to call Init/Update/View on, so this is a fail-fast guard before any terminal setup happens. The program returns p.initialModel (nil) alongside the error and no terminal state is touched.

Source

Thrown at tea.go:993

	_, okSSHTTY := environ.LookupEnv("SSH_TTY")
	_, okWTSession := environ.LookupEnv("WT_SESSION")

	return (!okTermProg && !okSSHTTY) ||
		okWTSession ||
		(okTermProg && !strings.Contains(termProg, "Apple") && !okSSHTTY) ||
		strings.Contains(termType, "ghostty") ||
		strings.Contains(termType, "wezterm") ||
		strings.Contains(termType, "alacritty") ||
		strings.Contains(termType, "kitty") ||
		strings.Contains(termType, "rio")
}

// Run initializes the program and runs its event loops, blocking until it gets
// terminated by either [Program.Quit], [Program.Kill], or its signal handler.
// Returns the final model.
func (p *Program) Run() (returnModel Model, returnErr error) {
	if p.initialModel == nil {
		return nil, errors.New("bubbletea: InitialModel cannot be nil")
	}

	// Initialize context and teardown channel.
	p.handlers = channelHandlers{}
	cmds := make(chan Cmd)

	p.finished = make(chan struct{})
	defer func() {
		close(p.finished)
	}()

	defer p.cancel()

	if p.disableInput {
		p.input = nil
	} else if p.input == nil {
		p.input = os.Stdin
		if !term.IsTerminal(os.Stdin.Fd()) {

View on GitHub (pinned to 351d2159f8)

Solutions

  1. Ensure the model passed to tea.NewProgram is a properly initialized, non-nil value
  2. Check the return of your model constructor before creating the program
  3. If the nil-ness comes from a typed nil pointer, guard for that in your constructor so NewProgram receives a real value
  4. Add a unit test that constructs the program exactly like main() does

Example fix

// before
var m tea.Model // nil
p := tea.NewProgram(m)

// after
m := newModel() // returns initialized model
p := tea.NewProgram(m)
Defensive patterns

Strategy: validation

Validate before calling

func newProgram(m tea.Model, opts ...tea.ProgramOption) *tea.Program {
    if m == nil {
        log.Fatal("model must not be nil")
    }
    return tea.NewProgram(m, opts...)
}

Type guard

func hasModel(m tea.Model) bool {
    if m == nil { return false }
    // catch typed-nil pointers of common shapes
    v := reflect.ValueOf(m)
    switch v.Kind() {
    case reflect.Ptr, reflect.Map, reflect.Slice, reflect.Interface:
        return !v.IsNil()
    }
    return true
}

Try / catch

_, err := p.Run()
if err != nil && strings.Contains(err.Error(), "InitialModel cannot be nil") {
    // programmer error: fix call site, don't retry
    panic(err)
}

Prevention

When it happens

Trigger: tea.NewProgram(nil) followed by Run; a constructor function returning a nil *concreteModel typed as tea.Model (typed-nil: non-nil interface holding nil pointer is caught here only if the interface itself is nil); passing a nil model variable because of an uninitialized struct or early-return in a builder.

Common situations: Factories like newModel(opts) that return nil on bad options but the caller forgets to check; typed-nil pitfalls where var m *myModel = nil is passed and methods are called on it (panics later instead); refactoring that removes model instantiation; conditional model creation where one branch returns nil.

Related errors


AI-assisted analysis of charmbracelet/bubbletea@351d2159f8 (2026-08-15). Data as JSON: /api/errors/878929e9e7f2f78b. Report an issue: GitHub.