Troubleshooting
Run and executor issues
Diagnose queued or stuck runs, executor registration failures, interrupted runs, and scheduler in-flight expiry.
Run and executor issues
Dispatch problems have three usual sources: the control plane's own concurrency gate, executor availability, and the lease mechanism that detects lost work. For each entry below, check the control plane's structured log (logs/envoy.log and container stderr), the executor's container stderr, and the run's own logs in the UI.
Run stays pending and never starts
Symptom: A run sits in pending. In HTTP mode the control-plane log may show No healthy executors available with sufficient capacity.
Cause: Either the control plane's runs.max_concurrent gate is full (new runs wait until an active run finishes), or no registered executor is healthy with free capacity — offline executor, all slots busy (--max-concurrent, default 10), or an executor key mismatch preventing registration.
Resolution:
- Open Executors: at least one executor must be healthy with active tasks below capacity.
- Check active runs against
runs.max_concurrent(default 50;0disables the gate). - If no executor is listed, see the registration entry below.
- Do not create repeat runs while capacity is unavailable; that hides the original dispatch failure. Note that a pending run untouched for 90 seconds is failed by the reaper (
PENDING_GRACE_MS).
Executor does not appear on the Executors page
Symptom: A started executor never shows up, or shows unhealthy immediately.
Cause: The executor could not register or heartbeat: missing ENVOY_EXECUTOR_KEY (the process exits at startup with [executor] ENVOY_EXECUTOR_KEY environment variable is required), a key that does not match the control plane's executor.config.executor_key, no control-plane URL, or a wrong URL.
Resolution:
- Check executor stderr for the startup error. Provide at least one
--control-planeURL orENVOY_CONTROL_PLANES; otherwise the command exits withError: at least one --control-plane URL or ENVOY_CONTROL_PLANES is required. - Confirm both sides use the same executor key value.
- Confirm the control-plane URL is reachable from the executor container, and the executor's
--advertise-urlis reachable from the control plane (service DNS name, notlocalhost). - Heartbeats run every 15 seconds — a healthy executor appears within one interval.
Run fails with "Executor became unavailable — run interrupted"
Symptom: A running task flips to failed with the error message Executor became unavailable — run interrupted.
Cause: The run's lease expired. Executor heartbeats renew leases for the run IDs they report; a lease lives 60 seconds (LEASE_TTL_MS, four missed 15-second heartbeats). When the executor dies, restarts, or loses network, the run reaper (sweeping every 30 seconds) fails the orphaned run rather than leaving it running forever.
Resolution:
- Check whether the executor restarted or lost connectivity at that time (container events, deploy history).
- Review the run's logs for partial external writes before rerunning — the task process was killed mid-flight.
- Prevent recurrence during deploys with the drain gate:
WAIT_FOR_DRAIN=true ./scripts/deploy-remote.sh.
Logs or completion arrive late after a control-plane restart
Symptom: After a control-plane restart, runs briefly show stale logs or a non-terminal status even though the executor finished the task.
Cause: When callbacks fail, the executor spools log chunks to disk (.envoy/.log-spool) and journals completions in a local SQLite journal (data/executor-journal.db), retrying delivery until the control plane returns.
Resolution: Wait for the executor's retry to drain, then reload the run. If a run was already reaped while the executor kept working, the executor is fenced on its next heartbeat: the control plane returns those run IDs in cancelRunIds and the executor kills the zombie processes.
Triggered job blocked, then suddenly fires again
Symptom: A Trigger skips executions while an old job run is stuck in flight, then resumes after roughly an hour.
Cause: The scheduler will not start a new triggered run while one is in flight. scheduler.max_inflight_age_minutes (default 60) caps how long an abandoned in-flight job run blocks its schedule; past that age the Trigger fires again.
Resolution:
- Find the stuck job run and resolve it — stop it or let the reaper fail it.
- Tune
scheduler.max_inflight_age_minutesin server config if your longest legitimate job runs exceed the default.
Where to find each log
- Dispatch decisions and reaper activity — control-plane stderr and
logs/envoy.log([run-reaper]lines). - Executor lifecycle, registration, spooling — executor container stderr (
[executor]lines). - Task output — the run's logs in the UI, stored in the configured run log store.
Related
- Control plane and executors — registration, heartbeats, leases, and callbacks
- Live run monitoring — distinguishing a stale page from a stuck run
- Server configuration —
runs.max_concurrent,executor, andschedulerkeys