Skip to main content

Debugging, common pitfalls, and tips

View Markdown

Most Nexus problems are wiring problems, and they produce a small number of recognizable symptoms. Work from the symptom.

The call hangs and nothing happens

Three causes, in the order worth checking.

No Worker is polling the target Task Queue. The request was accepted and queued, and nothing is serving it. Check that your handler Worker is running and shows as a poller on the Endpoint's target Task Queue.

The Task Queue does not match. The Endpoint's target Task Queue and the Task Queue your Worker registered are two separate strings that have to be identical. A typo produces exactly this symptom, because the request is queued somewhere nobody is listening.

The timeout is longer than your patience. A human approval with a multi-day schedule-to-close timeout is supposed to sit there. Confirm the Operation is actually pending rather than stuck by looking at it in the UI.

The call fails as unauthorized

The caller Namespace is almost certainly not on the Endpoint's allowed caller list.

Creating an Endpoint does not authorize anyone to call it. Endpoints reject callers that are not explicitly allowed, and in Temporal Cloud the Namespace name includes an Account suffix that is easy to omit. See Nexus security.

The caller and handler are not linked in the UI

The handler used its own Temporal Client instead of the one TemporalOperationHandler injects.

Fetching a Client yourself works, and the Operation behaves correctly, but you lose the bidirectional links that connect the two Executions. Use the injected Client for anything that starts or messages an Execution.

The one current exception is Query, where linking is still in progress and no SDK produces a link yet.

A Query returns stale data, hangs, or throws

Query handlers have two hard constraints, and violating either fails in confusing ways.

A Query must not block. It is served synchronously by replaying history. Waiting on anything stalls the Query rather than delaying it.

A Query must not mutate state. Changes made during a Query are not recorded in Event History, so they are invisible and will not survive. Return only what is already in memory.

If a Query against a completed approval fails, the Retention Period has probably expired and the history it needs to replay is gone.

Pitfalls that are easy to miss

Using a Query to get the final result

The single most common design mistake in this shape.

The approval's decision is the result of requestApproval — the Workflow's return value, pushed to the caller when the Workflow completes. Querying for it instead means polling for something already being delivered, it requires the Workflow code to stay deployed and replay-compatible, and it stops working when history ages out.

Use the Operation result for outcomes. Use a Query for in-flight progress.

Expecting to re-attach to a running Operation

There is no Operation that attaches to an already-running Execution and waits for its result. Get Workflow Result as an async backing is not yet available.

Whoever starts the Operation is who receives the result. If other systems need it, distribute it from the caller or notify them from the handler.

An Activity-backed Operation that will not cancel

An Activity is not interrupted by cancellation the way a Workflow is. Without heartbeating, a heartbeat timeout, and maximum attempts of 1, a cancellation request has no effect and the Operation runs to its timeout. All three settings are needed together. See Cancellation requires heartbeating.

Duplicate side effects on retry

The server retries Nexus start requests. If the backing Execution's Id is not derived from something stable, a retry starts a second one.

Derive the Workflow Id or Activity Id from the Nexus request Id, or from the Operation input when several Operations should share one Execution. This matters most for Operations with external side effects — a duplicate notification is a second message to a real person.

Sending a Signal to a Workflow that may not exist

A Signal to a missing Workflow fails. Use Signal-with-Start when the target may not be running yet; it starts the Workflow if needed and delivers the Signal either way.

More than one async backing per handler invocation

A handler can perform unlimited sync side effects but at most one async backing. Calling startWorkflow and startWorkflowUpdate in the same invocation is not a valid Operation. Compose sync side effects freely; pick one thing for the caller to await.

Hand-editing generated code

Generated files are marked as generated and are overwritten on the next run. When a generated name is wrong, fix it with a per-language naming override in the contract. See the Nexus Client Code Generator.

Letting the contract drift

Callers and handlers deploy independently, so both sides run different contract versions simultaneously. Adding an optional field is safe. Making a field required, removing one, or changing a type is not — it breaks whichever side deploys second.

Tips

Verify the wiring before writing a caller. Confirm the Endpoint exists, targets the right Namespace and Task Queue, and that a Worker is polling it. This eliminates most of the symptoms above before any caller code exists.

Set timeouts to match reality. A human approval measured in days needs a schedule-to-close timeout in days. Defaults are not tuned for human latency.

Let contract violations be BAD_REQUEST. The generated validators aggregate every violation into one error, so the caller learns everything that was wrong in one response instead of fixing fields one at a time. See Nexus error handling.

Use TemporalOperationHandler even when the Operation is trivial. An Operation that starts synchronous can later gain an async backing or a Signal without changing shape.

RESOURCES