<@ULVA73B9P> recently we're seeing different behav...
# ask-marvin
a
@Marvin recently we're seeing different behavior with errors raised in mapped tasks, a change that isn't caused by different prefect version but maybe a different response from the prefect API if that's possible? Previously it was possible to raise an error in a mapped task all the way up the stack, but now those errors aren't raising unless somewhere in the parent a PrefectFuture.result() is called. it's even letting other tasks get passed that future without raising the error, instead its allowing the control flow to continue executing even though an exception was raised inside the task. what's going on?
m
thought for 368.9 seconds
Short version: what you’re seeing is consistent with Prefect 3’s “lazy” futures. Exceptions from tasks (mapped or not) only surface when the corresponding PrefectFuture is resolved. If nothing resolves the future, your Python control flow continues. The API doesn’t “push” exceptions into your process — your code must “pull” results or pass them into a place where Prefect resolves them. When an error will surface - You call
future.result()
(default
raise_on_failure=True
) → raises the task’s exception. - You pass a future as an argument to another Prefect task/flow (not wrapped in
allow_failure
) → Prefect implicitly resolves it before running the downstream task and raises
UpstreamTaskError
if it failed. - The flow returns a value that contains futures → Prefect resolves futures to states and aggregates; failed states will fail the flow and raise on result retrieval for the flow. When it will not surface - You never call
.result()
or otherwise use the future in a context where Prefect resolves it. - You call
future.wait()
(waits but does not raise). - You pass futures around inside custom objects (e.g., dataclass/DTO) that Prefect does not inspect/unwrap — resolution is skipped so downstream tasks can “get past” them. - You’ve annotated with
allow_failure(...)
, which allows failed upstreams to be passed downstream without raising. This explains your report: “it’s letting other tasks get past that future without raising” usually means downstream code is seeing a PrefectFuture (or a custom container holding one), not the task’s result. Prefect won’t implicitly resolve/raise in ordinary Python control flow like
if future:
or when stashing the future inside a custom class. How to get the earlier “bubble up” behavior - Resolve immediately after mapping:
Copy code
futures = my_task.map(items)
  # Wait for completion (optional)
  from prefect.futures import wait
  wait(futures)

  # Raise on any failure
  for f in futures:
      f.result()  # raises on the first failed mapped item
- Or force resolution in one place:
Copy code
from prefect.futures import resolve_futures_to_results
  results = resolve_futures_to_results(futures)  # raises if any failed
- Or ensure you pass futures directly as inputs to a downstream Prefect task (not wrapped in a custom object), so Prefect resolves them and raises an
UpstreamTaskError
automatically. - Only use
allow_failure(...)
when you explicitly want to handle failed states downstream. If this still doesn’t match what you’re seeing, can you share: - Your Prefect version (3.x minor/patch) - A minimal snippet that shows the mapped task failing and the downstream task “getting past” it - Whether you’re passing the futures directly vs. inside a custom object, and whether
allow_failure
is used anywhere With a small repro, I can pinpoint whether this is expected lazy resolution, a wrapping/container edge case, or something else.
a
Copy code
from prefect import flow, task


@task
def mytask(value: int):
    if value == 1:
        raise ValueError(value)
    return value


@task
def mytask2(value: int):
    return value


@flow
def myflow():
    values = [i for i in range(3)]
    results = mytask.map(values)
    results2 = mytask2.map(results)
    for result in results2:
        print(result.result())


myflow()
it's not until line 21 (for result in results2) that we get an exception raised. shouldn't we have an exception raised before that?