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
- Ensure Render is always entered from the xray traversal that sets context.WithValue(ctx, KeyParent, parentNode) before recursing into children
- In custom renderers, mirror the pattern: read parent, create your root node, put it as the new KeyParent for child renders
- In unit tests, seed the context: ctx = context.WithValue(ctx, KeyParent, NewTreeNode(gvr, fqn))
- 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
- Always enter xray renderers through the traversal that seeds KeyParent
- In tests, build the context with context.WithValue(ctx, KeyParent, NewTreeNode(gvr, fqn))
- Propagate the derived context (ctx = context.WithValue(ctx, KeyParent, node)) to child renders — never context.Background()
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
- no application found in context
- expecting a factory but got %T
- no benchmark dir found in context
- no path specified in context
- expecting context GVR
AI-assisted analysis of derailed/k9s@2d3ccc6ba2 (2026-08-15).
Data as JSON: /api/errors/055cd1775f748a9b.
Report an issue: GitHub.