charmbracelet/bubbletea · critical · ErrProgramPanic

program experienced a panic

Error message

program experienced a panic

What it means

ErrProgramPanic is a sentinel error returned by Program.Run when the framework recovered from a panic that occurred inside your Model's Init, Update, View, or a Cmd. Bubble Tea catches panics by default (unless WithoutCatchPanics is set) so the terminal state is restored before the process dies. The actual panic value is logged and printed via p.recoverFromPanic, while Run returns an error wrapping this sentinel (combined with ErrProgramKilled).

Source

Thrown at tea.go:39

	"os/signal"
	"runtime"
	"runtime/debug"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"
	"syscall"
	"time"

	"github.com/charmbracelet/colorprofile"
	uv "github.com/charmbracelet/ultraviolet"
	"github.com/charmbracelet/x/ansi"
	"github.com/charmbracelet/x/term"
	"github.com/muesli/cancelreader"
)

// ErrProgramPanic is returned by [Program.Run] when the program recovers from a panic.
var ErrProgramPanic = errors.New("program experienced a panic")

// ErrProgramKilled is returned by [Program.Run] when the program gets killed.
var ErrProgramKilled = errors.New("program was killed")

// ErrInterrupted is returned by [Program.Run] when the program get a SIGINT
// signal, or when it receives a [InterruptMsg].
var ErrInterrupted = errors.New("program was interrupted")

// Msg contain data from the result of a IO operation. Msgs trigger the update
// function and, henceforth, the UI.
type Msg = uv.Event

// Model contains the program's state as well as its core functions.
type Model interface {
	// Init is the first function that will be called. It returns an optional
	// initial command. To not perform an initial command return nil.
	Init() Cmd

View on GitHub (pinned to 351d2159f8)

Solutions

  1. Look at the program output/stderr: recoverFromPanic prints the panic message and stack trace — fix the line it points to
  2. Temporarily run the program with tea.WithoutCatchPanics() option so the panic crashes with a full goroutine dump
  3. Reproduce in `go test` by calling Update/View directly with the message that triggered the panic
  4. Add nil/bounds guards for the state the panic references
  5. If the panic comes from inside the library itself, file an issue at github.com/charmbracelet/bubbletea with the stack trace

Example fix

// before
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    return m.items[m.sel], nil // panics when m.sel >= len(m.items)
}

// after
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    if m.sel >= len(m.items) {
        return m, nil
    }
    return m.items[m.sel], nil
}
Defensive patterns

Strategy: try-catch

Validate before calling

// before p.Run(): nothing to validate; panics surface at runtime.
// Optionally build the program with panic capture ON (default) and a logger:
// f, _ := tea.LogToFile("debug.log", "debug")
// p := tea.NewProgram(m, tea.WithLogger(log.New(f, "", log.LstdFlags)))

Type guard

func isProgramPanic(err error) bool {
    return err != nil && errors.Is(err, tea.ErrProgramPanic)
}

Try / catch

model, err := p.Run()
if err != nil {
    if isProgramPanic(err) {
        // terminal already restored; stack trace printed by recoverFromPanic
        os.Exit(2)
    }
    log.Fatal(err)
}

Prevention

When it happens

Trigger: Any panic inside code invoked by the event loop: nil pointer dereference in Update, index out of range in View, nil map writes, division by zero in a Cmd, or a panic in a method called during rendering. Only fires when Program.Run is executing and p.disableCatchPanics is false (the default).

Common situations: A Model field not initialized before first Update; a slice indexed by user input without bounds checks; a nil sub-model after a state transition; panics inside a Cmd goroutine that bubble up through the cmds channel handling; upgrading Bubble Tea major versions where View/Update signatures changed and a half-migrated model panics.

Related errors


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