Textualize/textual · error · CallbackError

unable to run callback {event.callback!r}; {error}

Error message

unable to run callback {event.callback!r}; {error}

What it means

Raised by Textual's message pump when a timer callback (e.g. set_interval / set_timeout callback) raises an unhandled exception. The timer fires, the callback is invoked, and any exception it throws is wrapped in CallbackError with the original error text appended.

Source

Thrown at src/textual/message_pump.py:918

        await invoke(event.callback)

    async def on_timer(self, event: events.Timer) -> None:
        if not self.app._running:
            return
        event.prevent_default()
        event.stop()
        if event.callback is not None:
            try:
                self.app.screen
            except Exception:
                self.log.warning(
                    f"Not invoking timer callback {event.callback!r} because there is no screen."
                )
                return
            try:
                await invoke(event.callback)
            except Exception as error:
                raise CallbackError(
                    f"unable to run callback {event.callback!r}; {error}"
                )

View on GitHub (pinned to 06dbeef4bb)

Solutions

  1. Fix the underlying exception shown after the semicolon in the message
  2. Wrap the callback body in try/except if failures are expected
  3. Guard against dead/removed widgets inside the callback before using them

Example fix

// before
def refresh(self) -> None:
    self.query_one("#status").update(self.api.status())
// after
def refresh(self) -> None:
    try:
        self.query_one("#status").update(self.api.status())
    except Exception:
        self.log("refresh failed")
Defensive patterns

Strategy: try-catch

Try / catch

def safe_tick(self) -> None:
    try:
        self._tick()
    except Exception:
        self.log.error("timer callback failed", exc_info=True)

Prevention

When it happens

Trigger: A callback passed to app.set_interval(), app.set_timeout(), or widget.set_interval() raises an exception (AttributeError, KeyError, network error, etc.) while the app is running.

Common situations: Timer callbacks that reference widgets that were removed, callbacks that hit external APIs which intermittently fail, or None dereferences in periodic refresh code.

Related errors


AI-assisted analysis of Textualize/textual@06dbeef4bb (2026-08-27). Data as JSON: /api/errors/238deb2638612f7d. Report an issue: GitHub.