temporalio/temporal · error

chasm.Snapshot() called on child node: %+v

Error message

chasm.Snapshot() called on child node: %+v

What it means

Node.Snapshot() produces a full NodesSnapshot of a CHASM component tree and, per its contract, must only be invoked on the root node (a nil parent) when the tree is not dirty. The method soft-asserts n.parent == nil and panics with the node details when called on a child node. This is a programming-error guard: calling Snapshot on a child would produce an incomplete or invalid snapshot.

Source

Thrown at chasm/tree.go:2514

		n.pendingUserMetadata = make(map[any]*sdkpb.UserMetadata)
	}

	n.needsPointerResolution = false

	// Reset per-node subtreeIsDirty on all nodes in the tree.
	for _, node := range n.andAllChildren() {
		node.subtreeIsDirty = false
	}
}

// Snapshot returns all nodes in the tree that have been modified after the given min versioned transition.
// A nil exclusiveMinVT will be treated as the same as the zero versioned transition and returns all nodes in the tree.
// This method should only be invoked on root CHASM node when IsDirty() is false.
func (n *Node) Snapshot(
	exclusiveMinVT *persistencespb.VersionedTransition,
) NodesSnapshot {
	if !softassert.That(n.logger, n.parent == nil, "chasm.Snapshot() should only be called on the root node") {
		panic(fmt.Sprintf("chasm.Snapshot() called on child node: %+v", n))
	}

	// TODO: add assertion on IsDirty() once implemented

	nodes := make(map[string]*persistencespb.ChasmNode)
	n.snapshotInternal(exclusiveMinVT, nodes)

	return NodesSnapshot{
		Nodes: nodes,
	}
}

func (n *Node) snapshotInternal(
	exclusiveMinVT *persistencespb.VersionedTransition,
	nodes map[string]*persistencespb.ChasmNode,
) {
	if n == nil {
		return

View on GitHub (pinned to bde624efd1)

Solutions

  1. Call Snapshot only on the root node of the tree (e.g. the node held by the tree/execution, not child nodes)
  2. To capture subtree state, use the appropriate node accessor/serialization APIs instead of Snapshot
  3. Ensure the tree is clean (not dirty) before snapshotting, as the API expects

Example fix

// before
childNode := ctx.Node() // nested component node
childNode.Snapshot(nil) // panics
// after
rootNode := tree.Root() // root node only
rootNode.Snapshot(nil)
Defensive patterns

Strategy: type-guard

Validate before calling

// only snapshot roots
if node.Parent() != nil {
    return errors.New("cannot snapshot a child node")
}

Type guard

func isRootNode(n *chasm.Node) bool { return n != nil && n.Parent() == nil }

Try / catch

defer func() {
    if r := recover(); r != nil {
        if s, ok := r.(string); ok && strings.Contains(s, "called on child node") {
            // fix call site to use the root node
            return
        }
        panic(r)
    }
}()

Prevention

When it happens

Trigger: Calling chasm Node.Snapshot() on a node obtained via child traversal (n.parent != nil) instead of the root node — e.g. calling Snapshot on a node returned from a context/child accessor rather than the tree root.

Common situations: Application code grabbing a node handle for a nested component and calling Snapshot on it to serialize partial state; test code asserting on subtrees via Snapshot; misuse of internal APIs during framework extensions.

Related errors


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