Textualize/textual · error · CallbackError
unable to run callback
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.
Solutions
- Fix the underlying exception shown after the semicolon in the message
- Wrap the callback body in try/except if failures are expected
- 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
- Keep timer callbacks short and defensive
- Log exceptions inside callbacks rather than letting them propagate
- Check widget.is_running before doing work in a tick
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
- Can not create a worker from a non-async function unless…
- Can't await screen.dismiss() from the screen's message…
- Can't call worker.wait from within the worker function!
- Request to run a non-async function as an async worker
- RLock.release called too many times
AI-assisted analysis of Textualize/textual@06dbeef4bb (2026-08-27).
Data as JSON: /api/errors/238deb2638612f7d.
Report an issue: GitHub.
Appendix: 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)