Enable workflows to act on async results
Minjun Seong
When a CircleCI workflow kicks off async work — an external service, another pipeline, or any long-running process — there's no native way to get results back into the workflow and act on them.
The only signal a workflow can receive today is a binary approve/reject on a hold job, with no data attached. This means you can't pass results back in, you can't make decisions based on what happened, and if you poll for a response you're burning credits on idle compute.
Examples of where this comes up: security scans where you need to route based on severity rather than just pass/fail, ML model training that returns governance metrics and artifact locations, infrastructure provisioning where you need status before proceeding, and pipelines triggering other pipelines where context needs to carry back.
Customers today work around this by building callback Lambdas, persisting results to external stores, and writing retrieval logic in downstream jobs — custom glue infrastructure that shouldn't be necessary.
We'd love to hear from you on your use cases where you ran into this challenge:
- What async work are your pipelines waiting on?
- How are you getting results back into your workflow today?
- What decisions do your pipelines need to make based on those results?
- What workarounds have you had to build?
I
Ian Rowland
Part 2 of 2
There are further workarounds layered on top of this. The system relies on fragile naming conventions for job names, parameter casing, and correlation identifiers, where a single character mismatch can silently wedge the workflow because the callback cannot find its target. We also have custom signalling to mark when the last workflow in a downstream pipeline has finished, regardless of whether it succeeded or failed. Approval jobs are repurposed as RPC return channels, which leaves the CircleCI UI showing “waiting for approval” on jobs no human should ever interact with, but in some failure cases those approvals must be manually cancelled.
The custom orb mostly exists to hide trigger, wait, and fail boilerplate, but compiled configs can end up embedding multiple copies of the orb logic, pushing configuration size limits. Changes to the custom orb code have to be carefully considered as the multiplicative insertions further bloat the compiled config that can cause the configuration size limit to be exceeded!
All of this complexity exists solely to work around the lack of first‑class asynchronous orchestration support.
What would actually solve this is first‑class fan‑out and fan‑in support for multi‑project orchestration, including a native “await external completion” step. That step should wait on one or more external pipelines, return a typed JSON payload that indicates both completion and success, expose that payload directly to downstream jobs, and support timeout and retry. A typed contract between parent and child pipelines would make correlation implicit instead of relying on fragile naming conventions.
Happy to clarify further if useful.
Photo Viewer
View photos in a modal