<@ULVA73B9P> When I run this flow : from prefect i...
# ask-marvin
r
@Marvin When I run this flow : from prefect import task, flow @task def create_value() -> str: return "1" @task def process_value(x: str): print('processing...', x) @flow(log_prints=True) def t1(): val = create_value() process_value(val) if name == "__main__": t1() I can see arrows in UI on /runs/flow-run/xxx page, which should represents dependencies between tasks. But when I run this flow : from prefect import task, flow @task def create_value() -> int: return 1 @task def process_value(x: int): print('processing...', x) @flow(log_prints=True) def t1(): val = create_value() process_value(val) if name == "__main__": t1() I cannot sess any arrows in UI. Then only think what was changed is type of returning values, str->int. I am using OSS on-prem edition, version is 3.4.8. Why is this happening ?
m
thought for 616.2 seconds
Short version: the arrows are drawn when the server can see a data dependency between task runs. In Prefect 3.x that’s inferred from the recorded task_inputs for each task run. In 3.4.8, passing small primitive values inline can sometimes drop that provenance — the consumer’s task_inputs ends up with just the literal value instead of a reference to the upstream task run — so the UI has no edge to draw. You hit that with `int`; your
str
case happened to keep the reference, hence the arrows. Yes, slightly maddening. No, you didn’t do anything wrong. Ways to make the arrows reliable: - Use futures (explicit dependencies)
Copy code
from prefect import task, flow

  @task
  def create_value() -> int:
      return 1

  @task
  def process_value(x: int):
      print("processing...", x)

  @flow
  def t1():
      val = create_value.submit()
      process_value.submit(val)

  if __name__ == "__main__":
      t1()
Passing `PrefectFuture`s guarantees the server records the upstream→downstream link, so the UI draws the edge every time. - Persist the producer’s result (helps provenance for inline calls)
Copy code
@task(persist_result=True)
  def create_value() -> int:
      return 1
Or set
PREFECT_RESULTS_PERSIST_BY_DEFAULT=true
for the run. Persisting results gives the recorder something concrete to reference in
task_inputs
, which usually restores the edge for synchronous
create_value()
process_value(...)
. Caveat: this increases result storage usage. Practical notes: - This isn’t tied to OSS vs Cloud; it’s about how Prefect records task_inputs in 3.4.8. With simple Python primitives, provenance can be lossy. - Even without arrows, execution order in your example is still correct; it’s just the visualization that’s missing the link. - If the graph matters to you and you like the synchronous style, the
persist_result=True
route is the least disruptive. If you want concurrency and robust graphs, use
.submit()
. If you’d like, I can open an issue with a minimal repro noting the int vs str discrepancy you observed in 3.4.8.
🙌 1
r
@Marvin Please open the issue, it would be nice, if the graph showed dependencies for all types out of the box.
m
thought for 57.8 seconds
I can’t open GitHub issues on your behalf, but I can hand you a ready-to-file report. Paste this into Prefect’s issue tracker and hit submit. Title UI graph missing dependency edges when passing primitive ints between tasks (Prefect 3.4.8) Body
Copy code
### Bug summary
In Prefect 3.4.8 (OSS, on-prem), the flow run UI sometimes does not draw dependency edges between tasks when a task’s output is a primitive `int` passed to another task via a synchronous call inside a flow. The same pattern with a `str` shows the edge as expected.

Using futures (`.submit()`) or enabling `persist_result=True` restores the edge, so this appears to be a provenance/task_inputs recording issue for primitive ints in synchronous calls.

### Minimal reproducible examples

Works (edge shown):
```python
from prefect import task, flow

@task
def create_value() -> str:
    return "1"

@task
def process_value(x: str):
    print("processing...", x)

@flow(log_prints=True)
def t1():
    val = create_value()
    process_value(val)

if __name__ == "__main__":
    t1()
Does not show edge (no arrow between tasks):
Copy code
python
from prefect import task, flow

@task
def create_value() -> int:
    return 1

@task
def process_value(x: int):
    print("processing...", x)

@flow(log_prints=True)
def t1():
    val = create_value()
    process_value(val)

if __name__ == "__main__":
    t1()
Workarounds that do show edges: - Use futures:
Copy code
python
  val = create_value.submit()
  process_value.submit(val)
- Or persist results:
Copy code
python
  @task(persist_result=True)
  def create_value() -> int:
      return 1
### Expected behavior The UI graph should display a dependency edge for downstream tasks whenever they receive the output of an upstream task, regardless of the value’s primitive type (int vs str) or whether the call is synchronous. ### Actual behavior With synchronous task calls, passing a primitive
int
loses the visible dependency edge in the UI, whereas passing a
str
does not. ### Additional context - The UI path where this is visible:
/runs/flow-run/<id>
- This seems tied to how
task_inputs
are recorded for small primitive values; they appear to be inlined rather than recorded as a reference to the upstream task run, so the edge is not drawn. ### Environment - Prefect: 3.4.8 (OSS, on-prem) - Python: [please fill in] - Orchestration DB: [please fill in, e.g., Postgres version] - How the flow is run: local
__main__
execution (no deployment) ### Screenshots / logs (optional) N/A ### Notes Even without edges, execution order is correct; this report is about visualization. The issue does not reproduce when using
.submit()
or
persist_result=True
. ``` If you want, I can turn this into a gist you can one-click copy, but alas, no robotic finger to press “Create issue” for you.