Automation Error Guide: 'Timed out waiting for approval' Workflow Stuck on Signal
Fix approval gate timeouts and workflows stuck waiting for a signal: diagnose lost signals, wrong workflow/run id, missing notifications, deadline config, and dead listeners.
- #automation
- #troubleshooting
- #errors
- #workflows
Overview
An approval-gate timeout means a workflow paused to wait for an external decision — a human approval, a manual gate, an inbound signal — and the signal never arrived within the configured deadline, so the gate timed out and the workflow failed or rolled back. Durable workflow engines (Temporal, Argo, GitHub Environments, Camunda, n8n Wait nodes) suspend at a gate and resume only when a specific signal/event targeting that exact execution is received. If the signal is sent to the wrong id, the notification never reached an approver, or the listener that delivers approvals is down, the workflow waits until its timeout and then errors.
You will see the timeout in the workflow history/log:
WorkflowExecutionFailed message="approval gate timed out: no 'approve' signal within 24h"
node wait-for-approval terminated: timeout (deadline 86400s)
Or, with no deadline set, a workflow simply stuck forever:
status: Running currentNode: wait-for-approval waitingFor: signal 'approve' age: 5d
It occurs at the gate — after a deploy requests sign-off, a refund awaits review, or a pipeline pauses for a manual stage. A gate that worked before can hang the moment the approver notification breaks, the signaler computes the wrong run id, or the approval webhook/listener dies.
Symptoms
- Workflow fails with “timed out waiting for approval” / “signal not received”.
- Or the workflow sits
Runningindefinitely on the wait step with no deadline. - Approvers report they never got a notification (or the approve link 404s/errors).
- Sending the approval appears to succeed but the workflow doesn’t advance.
# Temporal: confirm the workflow is parked on a wait and how long
temporal workflow show --workflow-id deploy-7781 -o json \
| jq -r '.events[-1].eventType'
WorkflowTaskCompleted
# Has any signal been delivered to this execution?
temporal workflow show --workflow-id deploy-7781 -o json \
| jq '.events[] | select(.eventType=="WorkflowExecutionSignaled") | .workflowExecutionSignaledEventAttributes.signalName'
(no output -> no signal ever arrived)
Common Root Causes
1. Signal sent to the wrong workflow/run id
The approver action signals a stale or mistyped id (or an old run after a Continue-As-New), so the live execution never receives it.
temporal workflow signal --workflow-id deploy-7781 --name approve --input '{"by":"jjoyner"}'
temporal workflow show --workflow-id deploy-7781 -o json \
| jq '[.events[] | select(.eventType=="WorkflowExecutionSignaled")] | length'
1
If a manual signal increments the count but the original approval didn’t, the approver’s signal was hitting the wrong id.
2. Approval notification never sent / approver never knew
The gate paused but the notification step (email/Slack/PagerDuty) failed silently, so no human ever approved.
grep -RniE "notify|slack|email|approval.*sent|webhook" /var/log/approvals/*.log | tail
ERROR notify-approver failed: 502 from slack webhook (gate=deploy-7781)
A failed notification means the gate waits on a decision nobody was asked to make.
3. Approval listener / webhook receiver is down
The component that receives the approve click and translates it into a signal is offline, so approvals are accepted by the UI but never reach the engine.
systemctl status approval-listener --no-pager | head -5
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8801/approve/healthz
● approval-listener.service Active: failed (Result: exit-code)
000
A dead listener swallows approvals; the gate times out despite people clicking approve.
4. Signal name mismatch
The workflow waits for signal approve but the signaler sends approved (or a different case), so the engine never matches it.
grep -RniE "signal|waitFor|setHandler|getSignalChannel" ./workflows | head
grep -RniE "signalName|--name" ./approval-listener | head
workflows/deploy.ts:40: const approve = wf.defineSignal('approve')
approval-listener/send.ts:12: name: 'approved'
approve vs approved never matches; the gate waits forever.
5. No deadline configured — stuck indefinitely
The gate has no activeDeadlineSeconds/timer, so a missed approval doesn’t fail loudly; the workflow just hangs and is never noticed.
temporal workflow show --workflow-id deploy-7781 -o json \
| jq '.events[] | select(.eventType=="TimerStarted")'
(no timer -> no deadline on the gate)
Without a deadline, a lost signal produces a silent stuck workflow rather than a clear timeout.
6. Deadline too short for human response time
The gate’s timeout is shorter than realistic approver turnaround (e.g., 30 min for an off-hours sign-off), so it expires before anyone responds.
grep -RniE "approvalTimeout|deadline|activeDeadlineSeconds|awaitWithTimeout" ./workflows | head
workflows/deploy.ts:42: await wf.condition(() => approved, '15m') // 15m for a human gate
A 15-minute human-approval window will time out routinely outside business hours.
Diagnostic Workflow
Step 1: Confirm the workflow is parked on the gate
temporal workflow show --workflow-id <WID> -o json | jq -r '.status, .events[-1].eventType'
A Running status sitting on the wait step (no recent signal events) confirms it’s gated, not crashed.
Step 2: Check whether any signal was delivered
temporal workflow show --workflow-id <WID> -o json \
| jq '[.events[] | select(.eventType=="WorkflowExecutionSignaled")]'
Zero signal events means the approval never reached this execution — focus on routing/listener/notification.
Step 3: Verify the notification actually went out
grep -RniE "notify|slack|email|webhook|approval.*sent" /var/log/approvals/*.log | tail
A failed notify step explains why no human approved; fix the notifier first.
Step 4: Test the approval listener and signal path end-to-end
systemctl status approval-listener --no-pager | head -5
temporal workflow signal --workflow-id <WID> --name approve --input '{"by":"test"}'
If a manual signal advances the workflow but real clicks don’t, the listener (or the id it computes) is the break.
Step 5: Confirm signal name and deadline configuration
grep -RniE "defineSignal|signalName|condition\(|deadline|activeDeadlineSeconds" ./workflows ./approval-listener
Align the signal names exactly, and set a deadline that exceeds realistic human turnaround so misses fail loudly rather than hang.
Example Root Cause Analysis
A deploy-approval workflow keeps failing at the wait-for-approval gate with “timed out: no ‘approve’ signal within 24h”, even though approvers insist they clicked Approve.
The history shows zero signals ever reached the execution:
temporal workflow show --workflow-id deploy-7781 -o json \
| jq '[.events[] | select(.eventType=="WorkflowExecutionSignaled")] | length'
0
So clicks aren’t becoming signals. A manual signal advances it fine, proving the workflow and signal name are correct — the break is between the UI and the engine. Checking the listener:
systemctl status approval-listener --no-pager | head -3
● approval-listener.service Active: failed (Result: exit-code) since 2 days ago
The approval listener — the service that receives the approve click and calls workflow signal — crashed two days ago. The UI accepted clicks and returned success, but nothing translated them into signals, so every gate ran out its 24h deadline.
Fix: restore the listener and re-run the failed deploys (or re-signal the still-open ones):
sudo systemctl restart approval-listener
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8801/approve/healthz
200
With the listener healthy, clicks deliver signals again and gated workflows resume on approval.
Prevention Best Practices
- Always set a gate deadline so a lost approval fails loudly (timeout) instead of leaving a workflow silently stuck forever — and size it to realistic human turnaround.
- Health-check the approval listener/webhook receiver and alert when it’s down; a dead listener accepts clicks the engine never sees.
- Make the approve action confirm delivery: surface the signal result to the approver so a failed signal isn’t reported as success.
- Define signal names in one shared place so the workflow and the signaler can’t drift (
approvevsapproved). - Verify the signaler computes the correct workflow/run id (handle Continue-As-New and stale ids), and log every signal with its target id.
- Send the approval notification reliably (retry the notify step) and alert when a workflow has been waiting past a soft threshold. The free incident assistant can flag long-parked gates; see more automation guides.
Quick Command Reference
# Is the workflow parked on the gate?
temporal workflow show --workflow-id <WID> -o json | jq -r '.status, .events[-1].eventType'
# Did any signal arrive?
temporal workflow show --workflow-id <WID> -o json \
| jq '[.events[] | select(.eventType=="WorkflowExecutionSignaled")]'
# Was the deadline timer set?
temporal workflow show --workflow-id <WID> -o json \
| jq '.events[] | select(.eventType=="TimerStarted")'
# Notification and listener health
grep -RniE "notify|slack|email|webhook|approval.*sent" /var/log/approvals/*.log | tail
systemctl status approval-listener --no-pager | head -5
# Manually deliver the signal end-to-end
temporal workflow signal --workflow-id <WID> --name approve --input '{"by":"test"}'
# Signal-name / deadline config in code
grep -RniE "defineSignal|signalName|condition\(|deadline|activeDeadlineSeconds" ./workflows ./approval-listener
Conclusion
An approval-gate timeout or stuck-waiting workflow means a required signal never reached the parked execution in time. The usual root causes:
- The approval signal was sent to the wrong workflow/run id.
- The approver notification was never delivered, so no one approved.
- The approval listener/webhook receiver is down and clicks never become signals.
- The signal name in the workflow doesn’t match what the signaler sends.
- No deadline is configured, so a lost signal hangs the workflow silently.
- The deadline is shorter than realistic human response time.
Check whether any signal reached the execution first — zero signals points at notification/listener/routing, while a deadline that’s simply too short points at config — and a deadline on every gate ensures misses fail loudly instead of hanging.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.