{"record":{"id":"15a3b135fb8f8c26","repo":"temporalio/temporal","slug":"panic-rec","errorCode":null,"errorMessage":"panic(rec)","messagePattern":"panic\\(rec\\)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"service/history/ndc/workflow_state_replicator.go","lineNumber":300,"sourceCode":"\tif err != nil {\n\t\treturn err\n\t}\n\tif emitLifecycle {\n\t\t// Snapshot the post-apply mutable state at release time: the only point that is both after\n\t\t// the apply (which mutates ms in place) and still under the workflow lock, since the apply\n\t\t// releases via the transaction manager.\n\t\tinnerReleaseFn := releaseFn\n\t\treleaseFn = func(err error) {\n\t\t\tif err == nil && appliedMS == nil && ms != nil {\n\t\t\t\tappliedMS = ms.CloneToProto()\n\t\t\t}\n\t\t\tinnerReleaseFn(err)\n\t\t}\n\t}\n\tdefer func() {\n\t\tif rec := recover(); rec != nil {\n\t\t\treleaseFn(errPanic)\n\t\t\tpanic(rec)\n\t\t}\n\t\treleaseFn(retError)\n\t}()\n\tif versionedTransitionArtifact.IsFirstSync {\n\t\t// this is the first replication task for this workflow\n\t\t// TODO: Handle reset case to reduce the amount of history events write\n\t\tcontinueProcess, err := r.handleFirstReplicationTask(ctx, archetypeID, wfCtx, versionedTransitionArtifact, sourceClusterName, captureMutableState)\n\t\tif err != nil || !continueProcess {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tms, err = wfCtx.LoadMutableState(ctx, r.shardContext)\n\tswitch err.(type) {\n\tcase *serviceerror.NotFound:\n\t\treturn r.applySnapshot(ctx, namespaceID, wid, rid, archetypeID, wfCtx, releaseFn, nil, versionedTransitionArtifact, sourceClusterName, captureMutableState)\n\tcase nil:\n\t\tlocalTransitionHistory := ms.GetExecutionInfo().TransitionHistory","sourceCodeStart":282,"sourceCodeEnd":318,"githubUrl":"https://github.com/temporalio/temporal/blob/bde624efd13fbd3843654058db6d9c716166318b/service/history/ndc/workflow_state_replicator.go#L282-L318","documentation":"In NDC WorkflowStateReplicator.Apply, a deferred recover() handler releases the versioned-transition artifact lock with errPanic semantics and then re-panics with panic(rec) to preserve the original panic. The re-panic is intentional: the lock must be released with the panic error before the panic propagates upward.","triggerScenarios":"Any code inside Apply's critical section panics (nil pointer on mutable state, invariant violation, index-out-of-range), triggering the deferred recover-and-repanic block.","commonSituations":"Corrupt or partially-loaded mutable state during replication; nil dereference on missing workflow context; bugs in replication task handling surfacing as panics.","solutions":["Inspect the panic stack trace above this frame to find the real panicking code and fix that bug.","Add validation/nil checks in the replication path that produced the nil/invalid value.","Reproduce with the offending replication task payload in a unit/integration test."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"defer func() {\n\tif rec := recover(); rec != nil {\n\t\tlogger.Error(\"replication panic\", \"stack\", string(debug.Stack()))\n\t\tpanic(rec)\n\t}\n}()","preventionTips":["Nil-check mutable state and workflow contexts in the replication apply path.","Reproduce with a unit test before deploying replication changes."],"tags":["history","replication","panic","lock-release"],"backgroundTag":"panic-recovered-and-reraised","analyzedSha":"bde624efd13fbd3843654058db6d9c716166318b","analyzedAt":"2026-09-01T07:18:39.080Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}