temporalio/temporal · error

version history not initialized

Error message

version history not initialized

What it means

IsLCAVersionHistoryItemAppendable compares a VersionHistory's last item against the given LCA item to decide if new events can be appended; it panics if the history has zero items. A VersionHistory must always contain at least one item (its initial branch point), so an empty Items slice indicates corrupted or improperly initialized state and the check aborts via panic.

Source

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

}

func SplitVersionHistoryByLastLocalGeneratedItem(
	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) {

View on GitHub (pinned to bde624efd1)

Solutions

  1. Construct histories only via versionhistory.NewVersionHistory(NewVersionHistoryItem(...)) so at least one item always exists.
  2. Before calling, skip or treat as non-appendable any history with len(v.GetItems()) == 0 instead of invoking the check.
  3. If trimming items, enforce a minimum of one item remains.
  4. Validate decoded branch protos after deserialization in history replay paths.

Example fix

// before
appendable := versionhistory.IsLCAVersionHistoryItemAppendable(vh, lcaItem) // panics if vh.Items empty
// after
if len(vh.GetItems()) == 0 {
    return false, nil
}
appendable := versionhistory.IsLCAVersionHistoryItemAppendable(vh, lcaItem)
Defensive patterns

Strategy: validation

Validate before calling

func isAppendableSafe(vh *historyspb.VersionHistory, lca *historyspb.VersionHistoryItem) bool {
    if vh == nil || len(vh.GetItems()) == 0 || lca == nil {
        return false
    }
    return versionhistory.IsLCAVersionHistoryItemAppendable(vh, lca)
}

Type guard

func isInitialized(vh *historyspb.VersionHistory) bool { return vh != nil && len(vh.GetItems()) > 0 }

Try / catch

// avoid the panic path by checking initialization first
if !isInitialized(vh) { return false, nil }
return versionhistory.IsLCAVersionHistoryItemAppendable(vh, lca), nil

Prevention

When it happens

Trigger: Calling versionhistory.IsLCAVersionHistoryItemAppendable(v, lcaItem) where v is a *historyspb.VersionHistory with len(v.Items) == 0 — e.g. a manually constructed &historyspb.VersionHistory{} or a branch whose items were truncated.

Common situations: Building version histories in tests with an empty struct instead of NewVersionHistory; deserialization of a branch that lost its items; code that filters/removes items down to zero during branch trimming and then re-checks appendability.

Related errors


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