SIGTERM. The worker stops taking new work, gives in-flight steps time to finish, then exits. Three settings control that sequence, and together they have to fit inside the time your platform allows before it sends SIGKILL.
What Happens on SIGTERM
SIGTERM, SIGINT, and SIGUSR2 start a graceful shutdown. With the defaults, counted from the signal:
Grace and force are both measured from the moment shutdown starts. They run in parallel and are not added together. Grace is when cancellation is requested, and force is the cap on how long the drain can take.
A second signal more than a second after the first exits immediately and abandons the drain.
The Settings
The time variables accept milliseconds or duration strings such as
90s. An empty value falls back to the default. The worker warns at startup when force time is not bigger than grace time. For the underlying options see Temporal’s WorkerOptions.
Sizing Against Your Platform
The worst case is the drain running to its bound, then the flush running to its own:.env
maxShutdownDelaySeconds and Deploys for the deploy lifecycle. On Kubernetes, terminationGracePeriodSeconds is described in Pod lifecycle.
Uncaught exceptions and unhandled rejections start the same drain, but it is capped at 60 seconds regardless of
TEMPORAL_SHUTDOWN_FORCE_TIME. Raising the settings only extends shutdowns started by a signal.What Happens to Work That Does Not Finish
When grace expires, Temporal sends a cancellation request to every step still running. That is all it does: Output does not pass the cancellation signal to your step’s HTTP or LLM calls, so a step stops only when its own code observes it. See Temporal’s cancellation guide and the activityContext.
- A step that honors cancellation ends its attempt as failed, and Temporal retries it on another worker right away.
- A step that ignores it keeps running until force time. The worker then abandons it and exits.
OUTPUT_ACTIVITY_HEARTBEAT_INTERVAL_MS) against a heartbeatTimeout of 5 minutes, so the retry starts within about 5 minutes of the deploy, on a worker that is still up. A step whose options set a longer heartbeatTimeout waits that long instead. OUTPUT_ACTIVITY_HEARTBEAT_ENABLED=false only stops the worker from sending heartbeats. The step’s heartbeatTimeout still applies, so it does not delay the retry. See Temporal’s activity failure detection and retry policies.
Workflow code is not affected. A workflow task the worker abandons times out and Temporal reschedules it on another worker, which replays the workflow from its history. See Temporal’s workflow task timeout.
Exit Codes
A service failing to stop is logged as a warning and never changes the exit code. If you alert on non-zero worker exits, a deploy no longer triggers one just because steps were still running.
The exit code does not report a worker that failed for another reason while draining. Temporal logs that as an error-level
Worker failed, and only the expired force timer is demoted to a warning. Alert on that log line, not on the exit code.
Reading the Shutdown Logs
The worker logs its budget when shutdown starts, so the numbers are in the log even if the platform kills the process before it exits:[level] namespace: message { fields }. Production logs are JSON with the same level, namespace, message and fields.
The Bye line is the last one, and its shutdownDurationMs is the time the whole shutdown took, hook flush included. Compare it with maxShutdownDelaySeconds or terminationGracePeriodSeconds to see how much margin you have.
When work was abandoned at force time you see this instead:
Worker failed is Temporal reporting that the force timer expired. Output logs it as a warning, since an expired force timer is an expected outcome of a deploy. IllegalStateError: Worker still in use follows from it: the abandoned steps still hold native calls, so Temporal cannot finish closing the worker. A shutdownDurationMs near shutdownForceTimeMs on Stopping Worker failed confirms the drain hit its bound.
OUTPUT_TEMPORAL_LOG_LEVEL controls these Temporal logs. At ERROR the Worker failed warning is not logged, because it is no longer an error.
If this happens on most deploys, your steps run longer than the window. Raise grace and force together with the platform window as shown above, or make long steps observe cancellation so they end at grace instead of at force.
Next Steps
Deployment
Deploy workers to Render, Railway, or your own platform.
Worker Tuning
Concurrency, polling, and memory settings for the worker.