can1357/oh-my-pi · error · TypeError

parallel() expects an iterable of zero-arg callables

Error message

parallel() expects an iterable of zero-arg callables

What it means

parallel() in the eval Python prelude runs zero-arg thunks concurrently via a shared pool that tracks task.maxConcurrency. Before scheduling, it validates that every item in the iterable is callable and raises TypeError immediately if any is not. This fail-fast check prevents a confusing mid-pool failure when a non-callable is invoked.

Source

Thrown at packages/coding-agent/src/eval/py/prelude.py:622

                i = futures[fut]
                try:
                    results[i] = fut.result()
                except BaseException as exc:  # noqa: BLE001 - propagate to caller
                    errors[i] = exc
        if errors:
            raise errors[min(errors)]
        return results

    def parallel(thunks):
        """Run zero-arg callables through a bounded pool, preserving input order.

        Barriers until all finish; re-raises the lowest-index exception if any
        thunk raised. Pool width tracks the task tool's ``task.maxConcurrency``.
        """
        thunks = list(thunks)
        for t in thunks:
            if not callable(t):
                raise TypeError("parallel() expects an iterable of zero-arg callables")
        return _pool_map(thunks, lambda t: t())

    def pipeline(items, *stages):
        """Map items left-to-right through one-arg stage callables.

        Every item clears stage N before any item enters stage N+1 (barrier per
        stage). Stage 1 receives the original item; later stages receive the
        previous stage's result. Pool width tracks ``task.maxConcurrency``.
        """
        current = _AwaitableList(items)
        for stage in stages:
            if not callable(stage):
                raise TypeError("pipeline() stages must be callables")
            current = _pool_map(current, stage)
        return current

    def log(message):
        """Emit a status ``log`` event for TUI rendering."""

View on GitHub (pinned to 9690622007)

Solutions

  1. Wrap argument-taking functions in lambdas or functools.partial: parallel([lambda: f(x) for x in xs])
  2. Check for accidental double-invocation — pass the function itself, not f() or await f()
  3. Filter or fix None values in the input list before calling parallel()
  4. Ensure every element is a zero-arg callable: parallel accepts thunks, not values or coroutines

Example fix

// before
parallel([fetch(url) for url in urls])
// after
parallel([lambda u=url: fetch(u) for u in urls])
Defensive patterns

Strategy: validation

Validate before calling

if (not isinstance(thunks, (list, tuple)) or any(not callable(t) for t in thunks)):
    raise TypeError("parallel() requires zero-arg callables")

Type guard

def is_zero_arg_callable(t) -> bool:
    return callable(t) and not isinstance(t, type)

Try / catch

try:
    results = parallel(thunks)
except TypeError as e:
    if "zero-arg callables" in str(e):
        thunks = [t if callable(t) else (lambda t=t: t) for t in thunks]
        results = parallel(thunks)
    else:
        raise

Prevention

When it happens

Trigger: Calling parallel() with an iterable containing anything not callable — e.g. parallel([fetch_data, None]), parallel(['task1', 'task2']), or passing coroutine objects / results of calling functions instead of functions.

Common situations: Passing partial results instead of thunks, mapping a list comprehension that already called the functions, forgetting a lambda wrapper around an argument-taking function, or a variable holding None due to an earlier failed lookup.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/c9409f8162a61e37. Report an issue: GitHub.