{"record":{"id":"238deb2638612f7d","repo":"Textualize/textual","slug":"unable-to-run-callback-event-callback-r-error","errorCode":null,"errorMessage":"unable to run callback {event.callback!r}; {error}","messagePattern":"unable to run callback (.+?); (.+?)","errorType":"exception","errorClass":"CallbackError","httpStatus":null,"severity":"error","filePath":"src/textual/message_pump.py","lineNumber":918,"sourceCode":"        await invoke(event.callback)\n\n    async def on_timer(self, event: events.Timer) -> None:\n        if not self.app._running:\n            return\n        event.prevent_default()\n        event.stop()\n        if event.callback is not None:\n            try:\n                self.app.screen\n            except Exception:\n                self.log.warning(\n                    f\"Not invoking timer callback {event.callback!r} because there is no screen.\"\n                )\n                return\n            try:\n                await invoke(event.callback)\n            except Exception as error:\n                raise CallbackError(\n                    f\"unable to run callback {event.callback!r}; {error}\"\n                )\n","sourceCodeStart":900,"sourceCodeEnd":921,"githubUrl":"https://github.com/Textualize/textual/blob/06dbeef4bb70fb718236aa418ed658ef4667a126/src/textual/message_pump.py#L900-L921","documentation":"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.","triggerScenarios":"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.","commonSituations":"Timer callbacks that reference widgets that were removed, callbacks that hit external APIs which intermittently fail, or None dereferences in periodic refresh code.","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"],"exampleFix":"// before\ndef refresh(self) -> None:\n    self.query_one(\"#status\").update(self.api.status())\n// after\ndef refresh(self) -> None:\n    try:\n        self.query_one(\"#status\").update(self.api.status())\n    except Exception:\n        self.log(\"refresh failed\")","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"def safe_tick(self) -> None:\n    try:\n        self._tick()\n    except Exception:\n        self.log.error(\"timer callback failed\", exc_info=True)","preventionTips":["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"],"tags":["timer","callback","async","textual"],"backgroundTag":"unhandled-callback-exception","analyzedSha":"06dbeef4bb70fb718236aa418ed658ef4667a126","analyzedAt":"2026-08-27T02:36:57.214Z","schemaVersion":2},"datasetVersion":"2026-08-27T03:17:27.898Z"}