<@ULVA73B9P> We're getting 422 errors when calling...
# ask-marvin
c
@Marvin We're getting 422 errors when calling a flow from another flow in Prefect 3.6.17. The error is "Client error '422 Unprocessable Entity' for url '.../api/task_runs/'" with the message "Input should be a valid dictionary or object to extract fields from". We noticed that in the error payload, all task_inputs parameters are being serialized as empty arrays instead of the actual values we're passing. Our setup is a parent flow calling a child flow directly like child_flow.main(param='value'), where both flows are sync and parameters are simple types like strings and None. We found that using .fn() as a workaround works fine, so child_flow.main.fn(param='value') executes without errors. We upgraded from 3.3.7 to 3.6.17 and this started happening. Are there known subflow parameter serialization issues in 3.6.17, and what changed between these versions that would cause parameters to serialize as empty arrays? Is using .fn() the correct workaround or is there a proper fix we should implement?
m
thought for 399.1 seconds
Thanks for the detailed report — that context is super helpful. Short answer - I can’t find a documented, known bug specifically about subflow parameter serialization causing 422s in 3.6.17. - Your observation that calling the child via
.fn()
works is consistent with bypassing Prefect’s subflow instrumentation (and therefore skipping the code path that builds
task_inputs
for the API). - The clean fix is not to rely on
.fn()
(you lose subflow observability, retries, caching, etc.). First try upgrading to the latest 3.6.x and ensure your client/server are on matching minor versions. If it still reproduces, please share a minimal repro and environment info and we’ll get it triaged. What I could find - I did not find an issue that exactly matches “empty arrays in task_inputs → 422” for 3.6.17. - There is a related 422 “create_task_run” error when running a deployment as a subflow (different validation error, but similar area of the code path): Error running subflow inside another flow. That one was on 3.4.4 and shows that subflow task-run creation is a sensitive area and has seen changes across 3.4–3.6. Why
.fn()
“works” -
.fn()
calls the underlying Python function directly and skips Prefect’s orchestration for that call. That means no subflow run is created, and no
task_inputs
are collected/sent — so you avoid the 422 path entirely. - Trade-off: you lose subflow run tracking, retries, caching, logs tied to a subflow, etc. It’s okay as a temporary workaround, but not ideal long-term. What likely changed between 3.3.7 and 3.6.x - There have been multiple changes in 3.4–3.6 around parameter resolution, result tracking, and subflow/parent linking. While I don’t have a single PR to point at for your exact symptom, it’s plausible a regression in how subflow parameters are collected/serialized led to your
task_inputs
being built incorrectly (showing up empty). Recommended next steps 1) Confirm version alignment - Are you on Prefect Cloud or self-hosted Server? - If self-hosted, what’s the API version? Please share the output of:
Copy code
prefect version
from: - the environment running the parent flow (client) - the Prefect server (API) - Prefect recommends keeping client and server on the same minor version. A mismatch can produce 422s if the schema changed. 2) Try the latest 3.6.x - If you can, upgrade to the latest 3.6.x (there have been a lot of patch fixes). If the issue persists there, we’ll treat it as a current bug. 3) Minimal repro - If possible, try this minimal example in your environment and share whether it reproduces:
Copy code
from prefect import flow

  @flow
  def child(param: str | None):
      return param

  @flow
  def parent():
      # This should create a subflow run
      return child(param="value")

  if __name__ == "__main__":
      parent()
- If it fails, please share: - Any debug logs around the POST to
/api/task_runs/
- A redacted snippet of the request body that shows
task_inputs
as empty arrays 4) Interim workarounds (if you must stay on 3.6.17) -
.fn()
is acceptable as a stopgap if you can accept the loss of subflow orchestration:
Copy code
child.fn(param="value")
- Alternatively, if you don’t need a true subflow, consider making the “child” a task inside the parent flow. If you can share the version info and a minimal repro, I can open a bug report with the Prefect team (or you can, if you prefer). I’ll also keep an eye out for any related fixes in the next patch releases.