temporalio/temporal · error

lcaItem is nil

Error message

lcaItem is nil

What it means

IsLCAVersionHistoryItemAppendable panics when the supplied LCA item pointer is nil. The function compares the history's last item to the LCA item via IsEqualVersionHistoryItem; a nil LCA means the caller's fork/merge computation failed to produce a common ancestor, which is a caller bug, so the guard fails fast before a nil dereference would occur inside the comparison.

Source

Thrown at common/persistence/versionhistory/version_history.go:204

	versionHistoryItems []*historyspb.VersionHistoryItem,
	initialFailoverVersion int64,
	failoverVersionIncrement int64,
) (localItems []*historyspb.VersionHistoryItem, remoteItems []*historyspb.VersionHistoryItem) {
	for i, versionHistoryItem := range slices.Backward(versionHistoryItems) {
		if versionHistoryItem.Version%failoverVersionIncrement == initialFailoverVersion {
			return versionHistoryItems[:i+1], versionHistoryItems[i+1:]
		}
	}
	return nil, versionHistoryItems
}

// IsLCAVersionHistoryItemAppendable checks if a LCA VersionHistoryItem is appendable.
func IsLCAVersionHistoryItemAppendable(v *historyspb.VersionHistory, lcaItem *historyspb.VersionHistoryItem) bool {
	if len(v.Items) == 0 {
		panic("version history not initialized")
	}
	if lcaItem == nil {
		panic("lcaItem is nil")
	}

	return IsEqualVersionHistoryItem(v.Items[len(v.Items)-1], lcaItem)
}

// GetFirstVersionHistoryItem return the first VersionHistoryItem.
func GetFirstVersionHistoryItem(v *historyspb.VersionHistory) (*historyspb.VersionHistoryItem, error) {
	if len(v.Items) == 0 {
		return nil, serviceerror.NewInternal("version history is empty.")
	}
	return CopyVersionHistoryItem(v.Items[0]), nil
}

// GetLastVersionHistoryItem return the last VersionHistoryItem.
func GetLastVersionHistoryItem(v *historyspb.VersionHistory) (*historyspb.VersionHistoryItem, error) {
	return getLastVersionHistoryItem(v.Items)
}

View on GitHub (pinned to bde624efd1)

Solutions

  1. Check lcaItem != nil before calling; if nil, treat the history as not appendable or return an internal error instead of panicking.
  2. Ensure the LCA is computed with FindLCAVersionHistoryItem on the same VersionHistories instance the branch belongs to.
  3. If histories legitimately share no ancestor, route through the non-LCA appendability path rather than fabricating a nil LCA.
  4. Add regression tests for fork/merge cases that previously produced nil LCA.

Example fix

// before
lca := versionhistory.FindLCAVersionHistoryItem(vh, target)
appendable := versionhistory.IsLCAVersionHistoryItemAppendable(vh, lca) // panics when lca nil
// after
lca := versionhistory.FindLCAVersionHistoryItem(vh, target)
if lca == nil {
    return fmt.Errorf("no LCA found between version histories")
}
appendable := versionhistory.IsLCAVersionHistoryItemAppendable(vh, lca)
Defensive patterns

Strategy: validation

Validate before calling

func appendableSafe(vh *historyspb.VersionHistory, lca *historyspb.VersionHistoryItem) (bool, error) {
    if lca == nil { return false, errors.New("no LCA version history item") }
    if len(vh.GetItems()) == 0 { return false, errors.New("version history not initialized") }
    return versionhistory.IsLCAVersionHistoryItemAppendable(vh, lca), nil
}

Type guard

func hasLCA(lca *historyspb.VersionHistoryItem) bool { return lca != nil }

Try / catch

// compute LCA and handle nil explicitly before the appendability check
lca := versionhistory.FindLCAVersionHistoryItem(vh, other)
if lca == nil { return internalErr }
return versionhistory.IsLCAVersionHistoryItemAppendable(vh, lca), nil

Prevention

When it happens

Trigger: Calling versionhistory.IsLCAVersionHistoryItemAppendable(v, nil), usually when an earlier call like FindLCAVersionHistoryItem / GetLCAVersionHistoryItem returned nil because no common-ancestor item was found between two branches.

Common situations: Comparing histories from two clusters/namespaces whose branch tokens diverged with no shared root; test code passing nil LCA to exercise appendability; bug in prepareBranch/getBranchToAppend-style logic that drops the LCA before the check.

Related errors


AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01). Data as JSON: /api/errors/263e20a1236a3589. Report an issue: GitHub.