{"record":{"id":"f45b4d6a902f8200","repo":"Textualize/textual","slug":"timed-out-while-waiting-for-widgets-to-process-pen","errorCode":null,"errorMessage":"Timed out while waiting for widgets to process pending messages.","messagePattern":"Timed out while waiting for widgets to process pending messages\\.","errorType":"exception","errorClass":"WaitForScreenTimeout","httpStatus":null,"severity":"error","filePath":"src/textual/pilot.py","lineNumber":524,"sourceCode":"\n        if count:\n            # Wait for the count to return to zero, or a timeout, or an exception\n            wait_for = [\n                asyncio.create_task(count_zero_event.wait()),\n                asyncio.create_task(self.app._exception_event.wait()),\n            ]\n            _, pending = await asyncio.wait(\n                wait_for,\n                timeout=timeout,\n                return_when=asyncio.FIRST_COMPLETED,\n            )\n\n            for task in pending:\n                task.cancel()\n\n            timed_out = len(wait_for) == len(pending)\n            if timed_out:\n                raise WaitForScreenTimeout(\n                    \"Timed out while waiting for widgets to process pending messages.\"\n                )\n\n            # We've either timed out, encountered an exception, or we've finished\n            # decrementing all the counters (all events processed in children).\n            if count > 0:\n                return False\n\n        return True\n\n    async def pause(self, delay: float | None = None) -> None:\n        \"\"\"Insert a pause.\n\n        Args:\n            delay: Seconds to pause, or None to wait for cpu idle.\n        \"\"\"\n        # These sleep zeros, are to force asyncio to give up a time-slice.\n        await self._wait_for_screen()","sourceCodeStart":506,"sourceCodeEnd":542,"githubUrl":"https://github.com/Textualize/textual/blob/06dbeef4bb70fb718236aa418ed658ef4667a126/src/textual/pilot.py#L506-L542","documentation":"Thrown when Textual's test Pilot times out waiting for all widgets on a screen to drain their pending message queues. It indicates some widget's message handler is blocked, long-running, or stuck.","triggerScenarios":"A message handler (on_*, watchers, actions) awaited something that never completes (e.g. await on an unresolved future, infinite loop, or a deadlock) while run_test(), press(), pause(), or wait_for_scheduled_animations() waits for idle.","commonSituations":"An event handler awaiting network I/O with no timeout, a worker started but awaited inline in a handler, or a modal waiting forever during automated tests.","solutions":["Find the blocking await in a message handler and offload it with self.run_worker(...)","Add timeouts to any awaits inside handlers","Increase test timeout passed to run_test(timeout=...) if processing is legitimately slow"],"exampleFix":"# before\nasync def on_click(self, event) -> None:\n    await self.load_everything()  # blocks message pump\n# after\ndef on_click(self, event) -> None:\n    self.run_worker(self.load_everything())","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    await pilot.pause()\nexcept WaitForScreenTimeout:\n    pytest.fail(\"a handler blocked the message pump; check workers/awaits\")","preventionTips":["Never block message handlers with unbounded awaits","Offload long work to workers","Set realistic run_test timeouts"],"tags":["testing","timeout","async","message-pump","textual"],"backgroundTag":"async-timeout","analyzedSha":"06dbeef4bb70fb718236aa418ed658ef4667a126","analyzedAt":"2026-08-27T02:36:57.214Z","schemaVersion":2},"datasetVersion":"2026-08-27T03:17:27.898Z"}