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:
- Keep Signal IDs and Flow names stable across deployments.
- Deploy the Flow target wherever wake delivery can execute it.
- Use a Runtime store that declares durable storage, implements Signal records, and has passed reactive composite conformance including transaction rollback at every write boundary.
- Confirm the Runtime supplies durable events, cursor reads, waiters, leases, atomic transactions, and at-least-once wake delivery.
- Use an explicit namespace appropriate for the deployment.
- Treat consumer code as retryable and keep external writes idempotent.
- 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
| Error | Code | Meaning |
|---|---|---|
SignalValidationError | invalid_payload | The authored payload failed its schema. issues contains bounded, sanitized issue paths. |
SignalError | idempotency_conflict | One Signal reused a key with different canonical normalized data. |
SignalError | publication_rejected | Validation 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
| Code | Boundary | Typical action |
|---|---|---|
CAPABILITY_MISSING | Flow activation | Supply the missing certified Runtime/store capability. |
TARGET_NOT_FOUND | Resume or wake | Deploy the named Flow target and compatible snapshot. |
TARGET_NOT_EXPORTED | Target loading | Export the deployed Flow definition from its module. |
PAYLOAD_NOT_JSON | Validation or replay | Normalize to finite, acyclic plain JSON and repair incompatible records. |
EVAL_REACTIVE_DISPATCH_FORBIDDEN | Eval publication | Publish 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:
| Symptom | What to check |
|---|---|
Worker start fails with CAPABILITY_MISSING | The 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 deploy | Confirm 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 advance | Poll/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 unavailable | Those 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 binding | Outcome counts use the first-64 adapter/binding statistics attribution. Overflow bindings report honest missing/other coverage rather than invented counters. |
| Stream never reopens after fault | Terminal 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.