Skip to main content
This guide covers behavior changes in the @outputai/core worker and in @outputai/llm cost reporting. Nothing in your code needs to change, but four runtime behaviors differ: the worker now bounds its own shutdown, every way it can end now has a defined outcome and exit code, a worker refuses to start unless it can publish a catalog matching its own source code, and LLM costs fall back to a bundled pricing snapshot instead of coming back empty.

Worker shutdown is now bounded

TEMPORAL_SHUTDOWN_GRACE_TIME and TEMPORAL_SHUTDOWN_FORCE_TIME have defaults: Previously the worker never force-terminated: it waited indefinitely for in-flight activities and was stopped only when the platform sent SIGKILL. It now stops draining after 25s, throws GracefulShutdownPeriodExpiredError, and exits under its own control - closing its Temporal connection, flushing hooks, and logging first. For most projects this is an improvement, because activities get 20 seconds of runway instead of being asked to cancel the instant a deploy starts.

Set both values if your activities drain slowly

If your activities routinely run longer than 25 seconds and you raised your platform’s shutdown window to accommodate them, set both variables explicitly. Otherwise those activities are cancelled at 20 seconds and abandoned at 25 on every deploy, then retried on the new instance. Keep grace below force, and force below your platform’s shutdown window, so the worker always exits before it is killed:
Values above your platform’s window never take effect, since the platform terminates the process first. On Render that window is maxShutdownDelaySeconds, which defaults to 30 seconds and can be raised to 300. On Kubernetes it is terminationGracePeriodSeconds. Raising these only extends shutdowns started by a signal. When an uncaught exception or unhandled rejection starts the shutdown instead, the worker force quits 60 seconds later regardless of TEMPORAL_SHUTDOWN_FORCE_TIME, exiting 1 after logging Uncaught exception handling timed out, force quitting.... That cap is fixed, so a force time above 60 seconds only fully applies to signal shutdowns. An empty value falls back to the default rather than disabling the bound, so there is no longer a way to configure an unbounded drain.

Shutdown scenarios

Every path that ends the worker changed shape. The exit code is also explicit now: v0.12.0 only called process.exit on the failure path and let the event loop empty on the success path. If you alert on non-zero worker exits, the situations that produce one changed: a drain that outlives its window now exits 1 under the worker’s own control instead of ending in a platform SIGKILL, and an uncaught error exits 1 only after draining. Two log lines moved with this. The failure log is now Worker error rather than Fatal error, and the last line is always Bye, where a failing worker used to end on Exiting....

Workers exit when the catalog does not match

The worker publishes its catalog during startup and treats it as a gate. If it cannot publish a catalog matching its own source code, it retries twice and then exits rather than serving requests against an outdated catalog. A worker that would previously have started with a stale catalog now fails its deploy instead. The failure is logged as Worker error with the underlying Temporal error. Reconciliation also changed: a stale catalog workflow is now terminated rather than asked to complete. A catalog that stopped processing workflow tasks no longer blocks every deploy that follows it.

LLM cost survives a pricing outage

@outputai/llm now ships a snapshot of the pricing table. When the live catalog is unreachable and nothing is cached, costs are calculated from that snapshot instead of coming back as null, and are reported with status: "imprecise" and pricingFreshness: "snapshot". If you branch on cost === null to skip cost accounting during an outage, that branch stops firing and you start recording snapshot-derived figures instead. Check pricingFreshness or status for that case. cost is still null when usage cannot be normalized, and a provider or model missing from the table still produces a non-null, incomplete cost.

Checklist

  • Set TEMPORAL_SHUTDOWN_GRACE_TIME and TEMPORAL_SHUTDOWN_FORCE_TIME explicitly if your activities need more than 25 seconds to drain, keeping both under your platform’s shutdown window.
  • Update alerts that key off worker exits or the Fatal error log line, which is now Worker error.
  • Expect a worker whose catalog cannot be published to exit instead of starting with a stale catalog.
  • Replace any cost === null check used to detect a pricing outage with a pricingFreshness or status check.