Tri
07/01/2026, 6:20 PMCompleted state with data from a task?
I have a Prefect task that returns Failed() when an exception occurs, so the flow does not crash.
On the happy path, I want the task to return data. Ideally, I would like to return something like:
return Completed(data=my_data)
instead of just:
return my_data
That way, the task signature can explicitly return a State.
But I cannot find how to attach data to Completed(). Is this possible in Prefect? What is the correct pattern here?Marvin
07/01/2026, 6:20 PMMarvin
07/01/2026, 6:23 PMCompleted(data=my_data) from a task.
That said, the usual pattern is:
- return plain Python data on success
- raise an exception on failure
- only return an explicit State when you intentionally want to control the task state yourself
So both of these are valid:
python
@task
def my_task():
return {"x": 1}
and
python
@task
def my_task():
return Completed(data={"x": 1})
In both cases, the task ends in Completed. The first is just simpler.
Important detail: if you return an explicit state, Prefect uses that state instead of inferring one from the return value.
Example:
python
from prefect import task
from prefect.states import Completed, Failed
@task
def my_task(should_fail: bool):
try:
if should_fail:
raise ValueError("boom")
my_data = {"status": "ok", "value": 42}
return Completed(data=my_data, message="Task succeeded")
except Exception as exc:
return Failed(data={"error": str(exc)}, message="Task handled failure")
A few notes on the correct pattern:
If your goal is “don’t crash the flow on expected errors”, returning Failed() is usually not the best fit, because the task is still marked failed.
Better options are:
1. Return normal data that encodes success/failure
python
@task
def my_task():
try:
return {"ok": True, "data": do_work()}
except Exception as exc:
return {"ok": False, "error": str(exc)}
2. Return Completed(data=...) even for handled/expected error cases
python
@task
def my_task():
try:
return Completed(data={"ok": True, "data": do_work()})
except Exception as exc:
return Completed(data={"ok": False, "error": str(exc)})
This keeps the task in a Completed state while still carrying structured output.
So the guidance is:
- Simple success path: return my_data
- Need explicit state control: return Completed(data=my_data)
- Handled business error that should not count as orchestration failure: return a completed result payload, not Failed()
- Actual task failure: raise an exception or return Failed(...)
Docs / references:
- Prefect states
- Prefect tasks
If you want, I can also show the best type-hinted pattern for a task that always returns either success-data or handled-error-data without using State in the function signature.Tri
07/01/2026, 6:24 PMTri
07/01/2026, 6:24 PMCompletedMarvin
07/01/2026, 6:24 PMMarvin
07/01/2026, 6:24 PMCompleted(data=...), you usually access the wrapped data via the task run’s .result().
Example:
python
from prefect import flow, task
from prefect.states import Completed
@task
def my_task():
return Completed(data={"x": 1, "y": 2})
@flow
def my_flow():
future = my_task.submit()
result = future.result()
print(result)
my_flow()
result will be:
python
{"x": 1, "y": 2}
If you're calling the task directly inside a flow:
python
@flow
def my_flow():
result = my_task()
print(result)
you should also get the underlying data, not the State object.
If you specifically want the state object and then inspect its data, use `return_state=True`:
python
from prefect import flow, task
from prefect.states import Completed
@task
def my_task():
return Completed(data={"x": 1})
@flow
def my_flow():
state = my_task(return_state=True)
print(state)
print(state.result())
my_flow()
Key distinction:
- future.result() → gives you the wrapped data
- state.result() → gives you the wrapped data
- the State object itself contains Prefect metadata, but .result() is the normal way to unwrap the payload
So in short: use .result().
Relevant docs:
- States
- Tasks
If you want, I can also show:
- how to inspect state.data directly
- how this behaves for Failed(data=...)
- the difference between .submit(), direct calls, and return_state=TrueTri
07/01/2026, 6:26 PMFailed instead of throwing exception because I don't want the exception to bubble up to fail the Flow, also I want to show the task as failed (red) in the UI, so I return the Failed state. Is there a better wayMarvin
07/01/2026, 6:26 PMMarvin
07/01/2026, 6:27 PMFailed() from the task is actually a reasonable pattern for your use case:
- the task shows as failed/red in the UI
- the flow does not automatically fail just because a task failed
- you keep control over whether the flow should fail or continue
That behavior is a core Prefect 3 change: task failure and flow failure are decoupled.
So if your goal is exactly:
- task should be red
- flow should continue / not crash
then this is fine:
python
from prefect import task
from prefect.states import Failed, Completed
@task
def do_work():
try:
data = {"value": 42}
return Completed(data=data)
except Exception as exc:
return Failed(message=str(exc))
However, there are two better-structured variants depending on what you need downstream.
---
Best pattern if you want red task + structured failure data
Return Failed(data=...) on error and Completed(data=...) on success:
python
from prefect import task
from prefect.states import Completed, Failed
@task
def do_work(x: int):
try:
if x < 0:
raise ValueError("x must be non-negative")
return Completed(data={"ok": True, "value": x * 2})
except Exception as exc:
return Failed(
message="Task failed but flow may continue",
data={"ok": False, "error": str(exc), "input": x},
)
Then in the flow, if you want to inspect the state explicitly:
python
from prefect import flow
@flow
def my_flow():
state = do_work(-1, return_state=True)
if state.is_failed():
error_info = state.result(raise_on_failure=False)
print("task failed:", error_info)
return "continuing anyway"
result = state.result()
print("task succeeded:", result)
A useful detail here:
- state.result() on a failed state will normally raise
- state.result(raise_on_failure=False) gives you the underlying payload/exception without re-raising
---
When this is better than raising exceptions
Returning Failed() is often better than raising if:
- the failure is an expected business outcome
- you want the UI to show failure
- you do not want control flow to be interrupted in the flow
That sounds like your case.
---
When raising is better
Raising an exception is better if:
- this is a true unexpected error
- you want retries to behave in the standard way
- you want the flow to decide failure via normal Python try/except
Though note: in Prefect 3, even raised task failures do not automatically fail the flow unless that failure is surfaced/returned/awaited in a way that propagates.
---
One caveat
If downstream tasks consume the failed task’s output, failed upstream states can block normal resolution unless you explicitly handle state/future behavior. So if you need downstream logic to continue based on a failed task, the cleanest approach is often:
- call task with return_state=True
- inspect state.is_failed()
- branch manually in the flow
Example:
python
from prefect import flow, task
from prefect.states import Completed, Failed
@task
def extract(flag: bool):
if flag:
return Failed(data={"reason": "bad input"})
return Completed(data={"rows": [1, 2, 3]})
@task
def fallback():
return "used fallback"
@flow
def pipeline(flag: bool):
state = extract(flag, return_state=True)
if state.is_failed():
info = state.result(raise_on_failure=False)
print("extract failed:", info)
return fallback()
data = state.result()
return data
---
Bottom line
For your stated goal, no, there isn’t necessarily a better way than returning Failed().
That is a good Prefect 3 pattern when you want:
- failed task state in UI
- no exception bubbling
- flow remains under your control
My recommendation:
- use Completed(data=...) on success
- use Failed(message=..., data=...) on handled failureMarvin
07/01/2026, 6:27 PMreturn_state=True if you need to inspect/branch on that result safely
Relevant docs:
- Prefect states
- Manage states
If you want, I can show a fully typed example of a task that returns `Completed`/`Failed` plus a flow that branches cleanly on the returned state.Tri
07/01/2026, 6:31 PMCompleted() and Failed()Marvin
07/01/2026, 6:31 PMMarvin
07/01/2026, 6:31 PMCompleted() / Failed() and shows they accept **kwargs, which are passed into the underlying State, including data.
2. The State schema/reference
- State object reference
The important bit is that State has a data field. Completed() and Failed() are helper constructors that build a State of the corresponding type, so data= works because it is a valid State field.
Conceptually, it is like this:
python
Completed(message="ok", data=my_data)
Failed(message="bad", data=my_error_payload)
because those helpers ultimately create a State(...) with that data.
3. Source-backed state docs
- States
This explains return-value behavior and explicit returned states, though it does not, as far as I can tell, emphasize data= as clearly as the API/source references do.
So the short answer is:
- narrative docs: mention explicit returned states, but not data= very clearly
- API reference/source: that’s where data= is really discoverable
If you want, I can point you to the exact Completed / Failed implementation lines in source and explain why data= is accepted.