Crux
GuidesDurable ExecutionSignals

Operations and errors

Diagnose Signal failures and deploy durable waits without overstating what acceptance proves.

Deployment checklist

Before relying on a durable Signal-to-Flow wait:

  1. Keep Signal IDs and Flow names stable across deployments.
  2. Deploy the Flow target wherever wake delivery can execute it.
  3. Use a Runtime store that declares durable storage, implements Signal records, and has passed reactive composite conformance including transaction rollback at every write boundary.
  4. Confirm the Runtime supplies durable events, cursor reads, waiters, leases, atomic transactions, and at-least-once wake delivery.
  5. Use an explicit namespace appropriate for the deployment.
  6. Treat consumer code as retryable and keep external writes idempotent.
  7. Keep normalized payloads small, JSON-safe, and within your data policy.

The default in-memory node() store is appropriate for process-local examples and tests that do not activate static Signal waits. It is not production durability.

Signal domain errors

ErrorCodeMeaning
SignalValidationErrorinvalid_payloadThe authored payload failed its schema. issues contains bounded, sanitized issue paths.
SignalErroridempotency_conflictOne Signal reused a key with different canonical normalized data.
SignalErrorpublication_rejectedValidation or durable acceptance could not complete safely.

Branch on the class and stable code, not the message:

import { SignalError, SignalValidationError } from "@use-crux/core/signal";

try {
  await orderSubmitted.publish(payload, { idempotencyKey });
} catch (error) {
  if (error instanceof SignalValidationError) {
    return { status: 400, issues: error.issues };
  }
  if (error instanceof SignalError && error.code === "idempotency_conflict") {
    return { status: 409 };
  }
  throw error;
}

Runtime errors around durable waits

CodeBoundaryTypical action
CAPABILITY_MISSINGFlow activationSupply the missing certified Runtime/store capability.
TARGET_NOT_FOUNDResume or wakeDeploy the named Flow target and compatible snapshot.
TARGET_NOT_EXPORTEDTarget loadingExport the deployed Flow definition from its module.
PAYLOAD_NOT_JSONValidation or replayNormalize to finite, acyclic plain JSON and repair incompatible records.
EVAL_REACTIVE_DISPATCH_FORBIDDENEval publicationPublish outside Eval or remove the armed durable consumer.

Capability and validation failures occur before acceptance and consume no occurrence identity. An accepted consumer attempt can later fail, retry, or dead-letter without changing the publication receipt.

Privacy boundary

Signal receipts and public errors omit raw idempotency keys, credentials, private payload fields, prompts, and consumer internals. Validation issue messages are sanitized and bounded. This protects diagnostics; it does not make stored payload data public-safe or secret-safe automatically.

No Signal API in this release promises historical subscription replay, consumer-completion acknowledgment, cross-process local callbacks, or restart-safe Effect rollback. Managed provider transports (webhook accept, polling, stream/SSE/WebSocket supervision) are covered separately under Signal providers and the providers and transports reference.

Managed transport troubleshooting

When diagnosing polling or stream bindings:

SymptomWhat to check
Worker start fails with CAPABILITY_MISSINGThe Runtime store must implement the transports port. Memory and PostgreSQL do; Convex does not claim managed-transport accept/checkpoints. Remove managed bindings or use a capable store.
Binding stays idle after deployConfirm the binding is on createRuntimeProgram({ providers, transports }) and one createRuntimeWorker owns the store/namespace. Competing workers coordinate through leases — only the lease holder acquires.
Cursor does not advancePoll/stream failures and envelope conflicts leave the durable cursor unchanged. Check transportBindingHealth fault.lastErrorCode and envelope state (accepted vs dead-letter).
Health shows reconnect / shutdown as unavailableThose facets are process-local and intentionally not durable. Durable status (active / faulted / disabled), last owner, and cursor age come from checkpoints only.
Counts missing on a bindingOutcome counts use the first-64 adapter/binding statistics attribution. Overflow bindings report honest missing/other coverage rather than invented counters.
Stream never reopens after faultTerminal faults set durable status: "faulted". Clear status or change secret-free configRef identity; automatic reconnect does not resume a faulted binding.

Operator health reads: transportBindingHealth({ store, namespace, program }). Devtools Runtime status includes a Transports tab when a generated program declares bindings; Run detail shows accepted-envelope lineage without payloads.

Finish with practical recipes, provider recipes, or the exact API reference.

On this page