derailed/k9s · error

expecting a TreeNode but got %T

Error message

expecting a TreeNode but got %T

What it means

The xray Service renderer (internal/xray/svc.go:39) retrieves the tree parent node from the render context (ctx.Value(KeyParent)) and asserts it to *TreeNode. The assertion fails when the context was not built by the xray tree traversal — i.e. Render was called without a parent already installed under KeyParent, or with a value of another type.

Source

Thrown at internal/xray/svc.go:39

// Service represents an xray renderer.
type Service struct{}

// Render renders an xray node.
func (s *Service) Render(ctx context.Context, ns string, o any) error {
	raw, ok := o.(*unstructured.Unstructured)
	if !ok {
		return fmt.Errorf("expected Unstructured, but got %T", o)
	}

	var svc v1.Service
	err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &svc)
	if err != nil {
		return err
	}

	parent, ok := ctx.Value(KeyParent).(*TreeNode)
	if !ok {
		return fmt.Errorf("expecting a TreeNode but got %T", ctx.Value(KeyParent))
	}

	root := NewTreeNode(client.SvcGVR, client.FQN(svc.Namespace, svc.Name))
	oo, err := s.locatePods(ctx, svc.Namespace, svc.Spec.Selector)
	if err != nil {
		return err
	}
	ctx = context.WithValue(ctx, KeyParent, root)
	var re Pod
	for _, o := range oo {
		p, ok := o.(*unstructured.Unstructured)
		if !ok {
			return fmt.Errorf("expecting *Unstructured but got %T", o)
		}
		if err := re.Render(ctx, ns, &render.PodWithMetrics{Raw: p}); err != nil {
			return err
		}
	}

View on GitHub (pinned to 2d3ccc6ba2)

Solutions

  1. Ensure Render is always entered from the xray traversal that sets context.WithValue(ctx, KeyParent, parentNode) before recursing into children
  2. In custom renderers, mirror the pattern: read parent, create your root node, put it as the new KeyParent for child renders
  3. In unit tests, seed the context: ctx = context.WithValue(ctx, KeyParent, NewTreeNode(gvr, fqn))
  4. Check the %T in the message to see what type actually occupied KeyParent

Example fix

// before: direct call
ctx := context.Background()
svc.Render(ctx, "ns1", obj) // -> expecting a TreeNode but got <nil>

// after: provide the parent
root := NewTreeNode(client.AllGVR, "cluster")
ctx := context.WithValue(context.Background(), KeyParent, root)
svc.Render(ctx, "ns1", obj)
Defensive patterns

Strategy: type-guard

Validate before calling

// validate the context value before rendering
parent, ok := ctx.Value(KeyParent).(*TreeNode)
if !ok {
    return fmt.Errorf("xray render requires a KeyParent *TreeNode; got %T", ctx.Value(KeyParent))
}

Type guard

func parentFrom(ctx context.Context) (*TreeNode, bool) {
    n, ok := ctx.Value(KeyParent).(*TreeNode)
    return n, ok
}

Prevention

When it happens

Trigger: Invoking xray Service.Render directly (tests, custom renderers) without the xray ContextBuilder that seeds KeyParent with a root *TreeNode; a context where KeyParent was overwritten with a different type.

Common situations: Writing custom xray renderers or reordering the render traversal; copying renderer code into tools that pass a plain context.Background(); refactors that drop the context.WithValue chain between parent and child renders.

Related errors


AI-assisted analysis of derailed/k9s@2d3ccc6ba2 (2026-08-15). Data as JSON: /api/errors/055cd1775f748a9b. Report an issue: GitHub.