The run was fully unattended. That was the point. Everything upstream of the final step had been automated for weeks, and the last remaining human touch was an approval — a request sent out, a wait, a decision, a continue-or-stop. We left it in place deliberately, because it felt like the responsible thing to keep.
Then the approval request timed out.
The interesting part is not that it timed out. The interesting part is what the log said about it. The log said the request failed. It did not say why, because from inside the process there is nothing to distinguish the two reasons a question can go unanswered. The destination could have rejected the request outright — bad credentials, wrong channel, no permission. Or the destination could have accepted it perfectly, displayed it exactly as intended, and had nobody standing there to look at it. Both produce the same silence. Both burn the same timeout window. Both write the same line.
We had built a gate that depended on a human being awake, and then we scheduled it at an hour we had specifically chosen because no human would be involved.
Once you see it, you cannot unsee how often this shape appears. An approval step is not a technical control. It is a request for attention, and attention has an availability window the way a service has an uptime window — except nobody writes it down, nobody monitors it, and nothing in the pipeline configuration mentions it. The gate looked like a dependency on a system. It was actually a dependency on a schedule of human presence that existed only in our heads.
The fix people reach for first is to raise the timeout. That does not fix anything; it just moves the failure later and makes the run slower on every successful path. The second reflex is to remove the gate entirely, which trades an ambiguous failure for an unreviewed action. Neither addresses the actual defect, which is diagnostic: we could not tell "no one home" from "access denied," and those two require completely opposite responses. One means fix the credentials or the destination. The other means the destination was fine and our operating assumptions were wrong.
So the first thing we changed was not the gate. It was the record. A failed approval now has to say which kind of failure it was — did the destination accept the request and simply never receive a reply, or did it never accept the request at all? That distinction exists in the response before the wait begins, and we had been discarding it because the outcome we cared about was pass or fail. We were throwing away the only field that told us where to look.
The second thing we changed was to stop pretending the gate was free. If a step requires a human, the run should know whether a human is plausibly reachable at that moment. An unattended run that dispatches a question into an empty room is not being cautious. It is generating a guaranteed timeout and calling it a safety property.
There is a broader discomfort here that I keep circling. We tend to describe automation as removing humans from the loop, but what usually happens is quieter: the human stays in the loop nominally and disappears from it practically. The step is still there. The request still gets sent. Everything looks supervised right up until the moment you check who was actually present, and the answer is nobody, and has been nobody for a while.
An unattended pipeline can only ever produce one of those two failures, and it is almost always the second one. The log does not know that. It writes the same line either way, and lets you believe you were refused when you were merely alone.
Top comments (0)