Self-Healing CI: Auto-Retrying a Transiently Timed-Out Step
A step that blows its time budget once and then passes on a clean retry was slow, not stuck - a transient slowdown, not a real hang.
The problem
A step is cancelled for exceeding its timeout, but the very next run completes well within the limit. Nothing changed in the code; a transient slowdown (a slow download, a busy dependency, a cold cache) pushed that one run over the edge. A human re-runs the job and it passes.
##[error] The action has timed out.
# or
Error: Step exceeded the timeout of 600 seconds and was cancelled.Why it happens
A timeout is a single threshold against wall-clock time, so any transient slowdown - a slow registry, a cold cache, brief CPU contention on a shared host - can push an otherwise-healthy step past the line on one run and not the next.
When the next run finishes comfortably under the same limit, the timeout was the symptom of a one-off slow moment, not a genuine deadlock or infinite loop.
The manual fix
Manual handling for a transient timeout:
- Re-run the job and confirm it completes within the limit.
- Warm caches and pin/mirror dependencies so the slow path is faster and steadier.
- Raise the timeout only enough to absorb normal variance - not so high that a real hang goes unnoticed.
How this gets automated
A transient timeout is distinguishable from a genuine hang by whether a retry completes in budget, so the safe response is a bounded retry. A self-healing CI pipeline retries a step that timed out transiently, and surfaces it only if it times out repeatedly - which is the real signal of a deadlock or runaway loop rather than a slow moment.