{"record":{"id":"e37305452b641f6c","repo":"charmbracelet/bubbletea","slug":"w-w","errorCode":null,"errorMessage":"%w: %w","messagePattern":"%w: %w","errorType":"panic","errorClass":"ErrProgramKilled/ErrProgramPanic","httpStatus":null,"severity":"critical","filePath":"tea.go","lineNumber":1029,"sourceCode":"\t\tif !term.IsTerminal(os.Stdin.Fd()) {\n\t\t\tttyIn, _, err := OpenTTY()\n\t\t\tif err != nil {\n\t\t\t\treturn p.initialModel, fmt.Errorf(\"bubbletea: error opening TTY: %w\", err)\n\t\t\t}\n\t\t\tp.input = ttyIn\n\t\t}\n\t}\n\n\t// Handle signals.\n\tif !p.disableSignalHandler {\n\t\tp.handlers.add(p.handleSignals())\n\t}\n\n\t// Recover from panics.\n\tif !p.disableCatchPanics {\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\treturnErr = fmt.Errorf(\"%w: %w\", ErrProgramKilled, ErrProgramPanic)\n\t\t\t\tp.recoverFromPanic(r)\n\t\t\t}\n\t\t}()\n\t}\n\n\t// Check if output is a TTY before entering raw mode, hiding the cursor and\n\t// so on.\n\tif err := p.initTerminal(); err != nil {\n\t\treturn p.initialModel, err\n\t}\n\n\t// Get the initial window size.\n\twidth, height := p.width, p.height\n\tif p.ttyOutput != nil {\n\t\t// Set the initial size of the terminal.\n\t\tw, h, err := term.GetSize(p.ttyOutput.Fd())\n\t\tif err != nil {\n\t\t\treturn p.initialModel, fmt.Errorf(\"bubbletea: error getting terminal size: %w\", err)","sourceCodeStart":1011,"sourceCodeEnd":1047,"githubUrl":"https://github.com/charmbracelet/bubbletea/blob/351d2159f8d8a85613aa2a6e98c8c63df3c98623/tea.go#L1011-L1047","documentation":"The exact error string produced at tea.go:1029 when Run's deferred recover() catches a panic: the returned error wraps both ErrProgramKilled and ErrProgramPanic, so errors.Is matches either sentinel. The program still calls recoverFromPanic, which restores the terminal and prints/logs the panic value and stack. This is the wrapped form of errors 0 and 1 appearing together.","triggerScenarios":"A panic anywhere in the Run call path after the recover is installed: eventLoop, Update, View, renderer calls, command execution. Only when catch-panics is enabled (default) — with WithoutCatchPanics the process crashes instead and no error is returned.","commonSituations":"Nil dereference in Update on an edge-case message; View returning inconsistent state after an error path; panics in third-party lipgloss/bubbles components; nil map assignment during Init; regressions found only via a rare message sequence.","solutions":["Read the printed panic + stack trace (recoverFromPanic outputs it) and fix the offending line","Re-run with tea.WithoutCatchPanics() for an unabridged crash dump if the recovered trace is truncated","Write a regression test driving Update/View with the failing message sequence","Check bubbles/lipgloss versions for known panics and upgrade","Report library-internal panics upstream with the stack"],"exampleFix":"// before\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n    switch msg := msg.(type) {\n    case tea.WindowSizeMsg:\n        m.width = msg.Width // but m is *model nil-able\n    }\n    return m, nil\n}\n\n// after\nfunc (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n    if m == nil { return &model{}, nil }\n    switch msg := msg.(type) {\n    case tea.WindowSizeMsg:\n        m.width = msg.Width\n    }\n    return m, nil\n}","handlingStrategy":"try-catch","validationCode":"// Nothing to pre-validate; instead run with a logger so the panic trace is preserved:\n// f, _ := tea.LogToFile(\"debug.log\", \"debug\")\n// p := tea.NewProgram(m, tea.WithLogger(log.New(f, \"\", 0)))","typeGuard":"func isKilledPanic(err error) bool {\n    return err != nil && errors.Is(err, tea.ErrProgramPanic) && errors.Is(err, tea.ErrProgramKilled)\n}","tryCatchPattern":"_, err := p.Run()\nif isKilledPanic(err) {\n    // stack already printed; exit with panic status\n    os.Exit(2)\n}","preventionTips":["Fuzz Update with random message orders in CI","Check nil receivers on pointer models","Keep View pure and total over the model state","Update bubbles/lipgloss together with bubbletea to avoid API-mismatch panics"],"tags":["bubbletea","panic","recover","runtime","go"],"backgroundTag":null,"analyzedSha":"351d2159f8d8a85613aa2a6e98c8c63df3c98623","analyzedAt":"2026-08-15T10:48:22.366Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}