{"record":{"id":"60e3e75735f20033","repo":"charmbracelet/bubbletea","slug":"program-experienced-a-panic","errorCode":null,"errorMessage":"program experienced a panic","messagePattern":"program experienced a panic","errorType":"error_code","errorClass":"ErrProgramPanic","httpStatus":null,"severity":"critical","filePath":"tea.go","lineNumber":39,"sourceCode":"\t\"os/signal\"\n\t\"runtime\"\n\t\"runtime/debug\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/charmbracelet/colorprofile\"\n\tuv \"github.com/charmbracelet/ultraviolet\"\n\t\"github.com/charmbracelet/x/ansi\"\n\t\"github.com/charmbracelet/x/term\"\n\t\"github.com/muesli/cancelreader\"\n)\n\n// ErrProgramPanic is returned by [Program.Run] when the program recovers from a panic.\nvar ErrProgramPanic = errors.New(\"program experienced a panic\")\n\n// ErrProgramKilled is returned by [Program.Run] when the program gets killed.\nvar ErrProgramKilled = errors.New(\"program was killed\")\n\n// ErrInterrupted is returned by [Program.Run] when the program get a SIGINT\n// signal, or when it receives a [InterruptMsg].\nvar ErrInterrupted = errors.New(\"program was interrupted\")\n\n// Msg contain data from the result of a IO operation. Msgs trigger the update\n// function and, henceforth, the UI.\ntype Msg = uv.Event\n\n// Model contains the program's state as well as its core functions.\ntype Model interface {\n\t// Init is the first function that will be called. It returns an optional\n\t// initial command. To not perform an initial command return nil.\n\tInit() Cmd\n","sourceCodeStart":21,"sourceCodeEnd":57,"githubUrl":"https://github.com/charmbracelet/bubbletea/blob/351d2159f8d8a85613aa2a6e98c8c63df3c98623/tea.go#L21-L57","documentation":"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).","triggerScenarios":"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).","commonSituations":"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.","solutions":["Look at the program output/stderr: recoverFromPanic prints the panic message and stack trace — fix the line it points to","Temporarily run the program with tea.WithoutCatchPanics() option so the panic crashes with a full goroutine dump","Reproduce in `go test` by calling Update/View directly with the message that triggered the panic","Add nil/bounds guards for the state the panic references","If the panic comes from inside the library itself, file an issue at github.com/charmbracelet/bubbletea with the stack trace"],"exampleFix":"// before\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n    return m.items[m.sel], nil // panics when m.sel >= len(m.items)\n}\n\n// after\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n    if m.sel >= len(m.items) {\n        return m, nil\n    }\n    return m.items[m.sel], nil\n}","handlingStrategy":"try-catch","validationCode":"// before p.Run(): nothing to validate; panics surface at runtime.\n// Optionally build the program with panic capture ON (default) and a logger:\n// f, _ := tea.LogToFile(\"debug.log\", \"debug\")\n// p := tea.NewProgram(m, tea.WithLogger(log.New(f, \"\", log.LstdFlags)))","typeGuard":"func isProgramPanic(err error) bool {\n    return err != nil && errors.Is(err, tea.ErrProgramPanic)\n}","tryCatchPattern":"model, err := p.Run()\nif err != nil {\n    if isProgramPanic(err) {\n        // terminal already restored; stack trace printed by recoverFromPanic\n        os.Exit(2)\n    }\n    log.Fatal(err)\n}","preventionTips":["Keep Model constructors total: never return nil models or partially initialized state","Write table-driven Update tests for every message type you handle","Guard slice/map access coming from user input or WindowSizeMsg","Run with a logger attached (tea.WithLogger + LogToFile) so panics are captured with traces","Use tea.WithoutCatchPanics only during debugging to get raw dumps"],"tags":["bubbletea","panic","tui","go","runtime"],"backgroundTag":null,"analyzedSha":"351d2159f8d8a85613aa2a6e98c8c63df3c98623","analyzedAt":"2026-08-15T10:48:22.366Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}