Textualize/textual · error · WorkerError

Unsupported attempt to run a thread worker

Error message

Unsupported attempt to run a thread worker

What it means

WorkerError raised in Worker._run_threaded: the work object is neither a coroutine, an awaitable, nor callable, so the thread-pool runner cannot execute it. This indicates a misconfigured Worker rather than a runtime failure.

Source

Thrown at src/textual/worker.py:322

            return run_awaitable(work())

        def run_callable(work: Callable[[], ResultType]) -> ResultType:
            """Set the active worker, and call the callable."""
            active_worker.set(self)
            return work()

        if (
            inspect.iscoroutinefunction(self._work)
            or hasattr(self._work, "func")
            and inspect.iscoroutinefunction(self._work.func)
        ):
            runner = run_coroutine
        elif inspect.isawaitable(self._work):
            runner = run_awaitable
        elif callable(self._work):
            runner = run_callable
        else:
            raise WorkerError("Unsupported attempt to run a thread worker")

        loop = asyncio.get_running_loop()
        assert loop is not None
        return await loop.run_in_executor(None, runner, self._work)

    async def _run_async(self) -> ResultType:
        """Run an async worker.

        Returns:
            Return value of the work.
        """
        if (
            inspect.iscoroutinefunction(self._work)
            or hasattr(self._work, "func")
            and inspect.iscoroutinefunction(self._work.func)
        ):
            return await self._work()
        elif inspect.isawaitable(self._work):

View on GitHub (pinned to 06dbeef4bb)

Solutions

  1. Pass a plain function to thread workers: `run_worker(my_function, thread=True)`.
  2. Ensure the object is callable (`callable(obj)`) before scheduling.
  3. Don't pass coroutines to thread workers — those take the async path.

Example fix

# before
self.run_worker(result, thread=True)
# after
self.run_worker(load_data, thread=True)
Defensive patterns

Strategy: validation

Validate before calling

import inspect
if not (inspect.isawaitable(work) or callable(work)):
    raise ValueError('work must be callable or awaitable')

Type guard

def is_runnable(w: object) -> bool:
    return callable(w) or inspect.isawaitable(w)

Try / catch

from textual.worker import WorkerError
try:
    self.run_worker(work, thread=True)
except WorkerError as e:
    log(e)

Prevention

When it happens

Trigger: Constructing Worker (or calling run_worker with thread=True) with a non-callable — e.g. an already-consumed coroutine's result, an int/str, or an object whose __call__ was removed. Rare; usually surfaces in tests or dynamic dispatch.

Common situations: Passing a variable that was accidentally awaited/consumed earlier; passing a class instead of an instance with __call__.

Related errors


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