How to Handle Timeouts and Long-Running Workflow Steps
A step that hangs can freeze an entire AI workflow. Here is how to set timeouts, handle long-running steps, and keep automation from stalling on a slow dependency.
One slow step can freeze an entire AI workflow. A model call that hangs, an API that never responds, a step waiting on something that will never arrive, and the whole run sits there consuming a slot and going nowhere. The fix is to put a timeout on every step, decide in advance what happens when it fires, and handle genuinely long-running work as a separate pattern rather than by just waiting longer. A workflow without timeouts is a workflow that can hang forever, and forever is a long time to hold a resource for nothing.
I run automation across a portfolio, and slow dependencies are a fact of life. External APIs have bad days. Models occasionally take far longer than usual. If my workflows blocked indefinitely every time a dependency was slow, a single bad afternoon at one provider would jam everything behind it. So every step has a deadline, and every deadline has a plan for when it is missed.
Why a missing timeout is a hidden outage
A step with no timeout will wait as long as the dependency makes it wait, which can be forever. While it waits, it holds resources: a worker slot, a connection, a place in the queue. Enough hung steps and you exhaust your capacity, and now healthy work cannot run because dead work is holding all the slots. A single unresponsive dependency becomes a system-wide stall.
The insidious part is that nothing errors. The step is not failing, it is waiting, and waiting looks like working to anything that only checks for exceptions. This is exactly the kind of silent problem a monitoring agent catches that plain error monitoring misses: throughput quietly drops to zero while every status says "in progress."
How to set timeouts on workflow steps
Every step gets a deadline. No exceptions. Set the timeout based on how long the step should reasonably take, plus headroom. A classification that normally returns in two seconds does not need a five-minute timeout, because five minutes of hanging is five minutes of a held slot for a step that is clearly broken at ten seconds.
Decide what firing means, per step. A timeout is not automatically a failure. For a transient slow dependency, the right move is often to retry with backoff, because the dependency may just be briefly overloaded. For a hard deadline you cannot miss, the right move is to fail fast and escalate. Classify each timeout the same way you classify errors: transient and retryable, or terminal and needs a human.
Cancel cleanly, do not just abandon. When a timeout fires, actually cancel the underlying operation. If you only stop waiting but leave the request running on the other side, you can get a late completion that lands after you have already moved on, causing a double effect. Clean cancellation plus idempotency is what keeps a timed-out step from doing its work twice.
Handle genuinely long-running work differently
Some steps are supposed to take a long time. A large batch job, a slow external process, a task that legitimately runs for minutes or hours. The wrong answer is a very long timeout, because a very long timeout still holds a slot the whole time and still hangs if the work never finishes.
The right pattern is to make long work asynchronous. Kick off the job, get a handle, and let the workflow pause instead of blocking. Then either poll for completion or wait for a callback, using the same webhook versus polling tradeoff you use for any trigger. The workflow parks, its slot is freed, and it resumes when the long job reports done. This is the difference between a step that waits three hours holding resources and a step that starts a three-hour job and gets out of the way.
This pause-and-resume behavior is the same machinery that makes approval gates work: a run that can cleanly park and resume can wait on anything, whether a slow job or a human, without holding capacity hostage.
Timeouts are a capacity decision, not just an error setting
I think about timeouts in terms of resource protection. Every second a step is allowed to hang is a second of capacity spent on nothing. Tight, sensible timeouts keep the system's capacity flowing to work that can actually make progress. They also surface broken dependencies fast: a step that times out immediately tells you the dependency is down, instead of hiding the problem behind a long, patient wait.
We build per-step timeouts, clean cancellation, and async pause-and-resume for long jobs into Girard AI, so a slow dependency degrades one step instead of freezing the whole system. When you evaluate an automation platform, ask what happens when a step hangs and never returns. If the answer is "it waits," then one bad API can take your entire operation down with it, quietly, with every status light still green.