Crux
GuidesBackground Work

Troubleshooting

Diagnose defer scope, retention, capability, and durable-acceptance failures.

Public defer failures use CruxDeferError with a stable code and docsUrl:

import { CruxDeferError, defer } from "@use-crux/core";

try {
  defer(() => {});
} catch (error) {
  if (error instanceof CruxDeferError) {
    console.error(error.code, error.docsUrl);
  }
}

Start with the ladder

  1. Is the call inside a Crux primitive? Agent turns, adapter calls, tools, and Safety sessions open scopes automatically.
  2. Can the process stay alive? On Next or Vercel, add the platform binding to config({ host }). On Workers, use a per-request boundary with ctx.
  3. Is the call at handler root? An ambient-capable config binding gives each root-level call an ephemeral invocation.
  4. Does the handler outcome or commit barrier matter? Add the platform wrapper for one grouped, classified invocation.
  5. Is this a replayable flow? Use flow.defer(), never public defer().

Error map

CodeMeaningFirst fix
DEFER_SCOPE_REQUIREDNo execution scope and no ambient host bindingMove inside a Crux primitive or configure the platform host
DEFER_CAPABILITY_MISSINGThe active host cannot honor this overload or cannot retain itSupply after/waitUntil/Workers ctx, or use named work on a named-only host
DEFER_SCOPE_SEALEDRegistration arrived after the accepting scope sealedRegister before the boundary returns; do not leak scope-bound callbacks
DEFER_LIMIT_EXCEEDEDCallback or nesting limits were exceededReduce fan-out/nesting or move durable fan-out into Runtime work
DEFER_REPLAY_UNSAFEPublic defer() ran inside replayable flow executionUse flow.defer()
DEFER_TARGET_INPUT_REQUIREDA named target has no required JSON inputPass the target's inferred input
DEFER_COMMIT_FAILEDStrict named staging/finalization failed before response commitmentFix Runtime acceptance; catching the named promise cannot restore success

Runtime-bound failures may also use Runtime codes such as RUNTIME_REQUIRED.

Common cases

It works locally but disappears on serverless

The local process stays alive; the deployment freezes. Install the platform package and configure its binding:

export default config({ host: next() });

For Workers, pass the current request's ctx through withCrux or a custom per-request binding. Never reuse another request's context.

A retention call throws after my function returned

The scope seals before the original retention error propagates. This is an acceptance failure, not a callback failure: Crux cannot promise the host will keep accepted work alive. The already-started drain remains best-effort.

Named work stages but the response is not protected

Config-only ambient mode makes your await defer(target, input) the acceptance barrier. It does not classify the handler outcome or poison a later response. Use a strict wrapper with durable finalization when response commitment depends on the named work.

Project checks

crux setup inspects source and package evidence. On a detected freezing platform it recommends the exact config({ host: ... }) line when no binding or strict wrapper is proven. crux setup --apply only applies safe additive Runtime resources; host integration remains an explicit code change.

Project Index may report:

  • defer.replay_unsafe
  • defer.floating_named_promise
  • runtime.missing_runtime_config

Bundled lints report only properties decidable from source. Run crux setup --check for config- and environment-dependent host retention; runtime capability checks remain authoritative.

On this page