Conversation
Reporting workflow errors and panics through an IsReplaying-guarded local activity records a MarkerRecorded event on the original execution that is never re-issued on replay, permanently failing the run with TMPRL1100. Report inline from the workflow goroutine instead, which emits no commands. Also fix the panic path swallowing filtered panics: the deferred recover now always re-raises. Removes WithWorkflowErrorActivityOptions and WithWorkflowPanicActivityOptions, which configured the no-longer-used local activities. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Queries are never re-delivered by replay, and a cold worker that just rebuilt state from history serves them with IsReplaying()==true — a blanket guard silently drops those errors. Update validators never run during replay at all. Guard only the handlers that re-execute from history: ExecuteWorkflow, HandleSignal and ExecuteUpdate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Fixes a determinism bug in workflow error/panic reporting by removing local-activity based reporting (which records a MarkerRecorded event) and switching to inline Sentry capture that emits no workflow commands, preventing permanent TMPRL1100 nondeterminism on replay.
Changes:
- Remove workflow local activities for reporting; report inline from the workflow goroutine with replay-aware suppression.
- Fix workflow panic handling so filtered panics are still re-raised, and query/validator reporting paths don’t get incorrectly suppressed.
- Add workflowcheck configuration + regression tests ensuring reporting emits no local-activity commands.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| workflow.go | Replaces local-activity reporting with inline Sentry capture; adjusts replay suppression semantics; fixes panic propagation behavior. |
| workflow_test.go | Adds regression tests verifying no local activity is executed for reporting, panics always propagate, and query errors are reported. |
| workflowcheck.config.yaml | Configures workflowcheck to allow the capture helpers (non-deterministic code that emits no commands). |
| Makefile | Passes the workflowcheck config to lint-workflows. |
| options.go | Removes workflow local-activity option fields and option setters that no longer apply. |
| interceptor.go | Updates top-level documentation to reflect inline workflow reporting constraints. |
| interceptor_test.go | Removes tests and setup for the removed workflow activity-options API. |
| README.md | Removes documentation for removed local-activity options and updates Sentry transport guidance. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+139
to
143
| r := recover() | ||
| if r == nil { | ||
| return | ||
| } | ||
|
|
| ## Note on Sentry configuration | ||
|
|
||
| It is recommended that Sentry's `HTTPSyncTransport` is not used, as all calls to `sentry.CaptureException` and `sentry.Recover` will block on the request being captured to Sentry if that transport is used. If the application's network connection to Sentry's servers is unreliable or unavailable it will cause issues due to the constraints that Temporal's local activities have of not being able to run for more than the amount of time a workflow task can (default is 10 seconds). | ||
| Sentry's `HTTPSyncTransport` must not be used. Workflow errors and panics are reported inline from the workflow goroutine — with an asynchronous transport (Sentry's default) `sentry.CaptureException` and `sentry.Recover` only enqueue the event and return immediately, but a synchronous transport would block the workflow task on network I/O and trip the Temporal SDK's deadlock detector (1 second). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Workflow error and panic reporting ran a local activity, guarded by
workflow.IsReplaying:IsReplayingmay only guard side effects that produce no commands (logging, metrics).ExecuteLocalActivityproduces one: it writes aMarkerRecordedevent into workflow history. Guarding it means the command is emitted on the original execution and never again — from the first replay onward the replayed command stream no longer matches history and every workflow task fails with:There is no recovery: the marker is in history for good, replay always starts from the first event, and the server retries forever. Any non-nil handler error is enough to poison a run —
ExecuteWorkflow,HandleSignal,HandleQuery,ValidateUpdateandExecuteUpdateall route through this path. This is the same defect just root-caused in a wedged productionwallet-serviceworkflow (uphold/wallet-service#465), whose interceptor is a sibling of this library; the failure was reproduced from the recorded production history withworker.WorkflowReplayer, confirmed against SDK v1.35.0 and server v1.29.6 source, and reproduced end-to-end with a minimal synthetic workflow.Fix
Report inline from the workflow goroutine —
CaptureException/RecoverWithContextonly enqueue on Sentry's async transport, so no command is emitted and the workflow task never blocks.The
IsReplayingguard now does the one thing it is valid for — suppressing duplicate reports during replay — and only on the handlers that actually re-execute from history (ExecuteWorkflow,HandleSignal,ExecuteUpdate). Queries are never re-delivered by replay, yet a cold worker that just rebuilt state from history serves them withIsReplaying() == true, so a blanket guard silently drops those errors; update validators never run during replay at all. Both now capture unconditionally.Two more defects fixed along the way:
recover()returned without re-panicking whenfilterWorkflowPanicmatched, letting the workflow continue as if the panic never happened. The recover now always re-raises.ExecuteLocalActivitypanics.Breaking change
WithWorkflowErrorActivityOptionsandWithWorkflowPanicActivityOptionsare removed — they configured the local activities that no longer exist. The README section documenting them is gone, and the transport note now reflects the real constraint: Sentry must use its (default) async transport, since a synchronous transport would block the workflow goroutine and trip the SDK's 1s deadlock detector.workflowchecknow needsworkflowcheck.config.yaml: the capture helpers are non-deterministic code by design, but emit no commands, which is what determinism actually requires. The Makefile passes the config.How has this been tested
make format,make lint(0 issues),make lint-workflows,make test— all green.workflow_test.goassert that error and panic reporting fire without executing any local activity (viaSetOnLocalActivityStartedListener), that panics — filtered or not — still propagate, and that query-handler errors are reported. All three reporting tests fail against the previous implementation.TMPRL1100above, and a minimal dev-server scenario (update handler error → worker restart → replay) wedges with the old pattern and completes cleanly with this one.Deploy notes
Fixes new runs only. Any run that already recorded a reporting marker still has it in history and will keep failing replay; those runs need a terminate (a new run can start under
ALLOW_DUPLICATE_FAILED_ONLY) or a reset to a workflow task before the marker.🤖 Generated with Claude Code