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
- Ensure the model passed to tea.NewProgram is a properly initialized, non-nil value
- Check the return of your model constructor before creating the program
- If the nil-ness comes from a typed nil pointer, guard for that in your constructor so NewProgram receives a real value
- 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
- Construct and validate the model in one place (constructor that never returns nil)
- Fail fast in main(): check constructor errors before NewProgram
- Beware typed-nil: returning (*myModel)(nil) as tea.Model passes nil-check only at interface level — normalize with a value receiver or explicit nil check
- Add a smoke test that builds the program as main does
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
- program experienced a panic
- program was killed
- program was interrupted
- bubbletea: error closing screen writer: %w
- bubbletea: error writing to screen: %w
AI-assisted analysis of charmbracelet/bubbletea@351d2159f8 (2026-08-15).
Data as JSON: /api/errors/878929e9e7f2f78b.
Report an issue: GitHub.