derailed/k9s · error
not a metav1.Table
Error message
not a metav1.Table
What it means
Workload.fetch powers the aggregated workloads view: it asks Table.List (server-side Table conversion, Accept: as=Table) for each workload GVR and asserts the first returned object is *metav1.Table. If the API server (or something in between) returns a plain object instead of a Table, the type assertion fails. It also fails when the table row is a custom type injected by a custom view decorator.
Source
Thrown at internal/dao/workload.go:89
if client.IsClusterScoped(ns) {
return dial.Delete(ctx, n, opts)
}
return dial.Namespace(ns).Delete(ctx, n, opts)
}
func (a *Workload) fetch(ctx context.Context, gvr *client.GVR, ns string) (*metav1.Table, error) {
a.gvr = gvr
oo, err := a.Table.List(ctx, ns)
if err != nil {
return nil, err
}
if len(oo) == 0 {
return nil, fmt.Errorf("no table found for gvr: %s", gvr)
}
tt, ok := oo[0].(*metav1.Table)
if !ok {
return nil, errors.New("not a metav1.Table")
}
return tt, nil
}
// List fetch workloads.
func (a *Workload) List(ctx context.Context, ns string) ([]runtime.Object, error) {
oo := make([]runtime.Object, 0, 100)
for _, gvr := range resList {
table, err := a.fetch(ctx, gvr, ns)
if err != nil {
return nil, err
}
var (
ns string
ts metav1.Time
)
for _, r := range table.Rows {View on GitHub (pinned to 2d3ccc6ba2)
Solutions
- Verify the server supports server-side tables: kubectl get <resource> -o json with Accept as=Table, or `kubectl get --raw` with the Table accept header.
- Bypass middlewares/proxies that rewrite API responses and retry the workloads view.
- If implementing a custom Table DAO, make sure List returns []*metav1.Table as the stock implementation does.
Example fix
// before
tt, ok := oo[0].(*metav1.Table)
if !ok { return nil, errors.New("not a metav1.Table") }
// after
tt, ok := oo[0].(*metav1.Table)
if !ok { return nil, fmt.Errorf("gvr %s: expected *metav1.Table, got %T", gvr, oo[0]) } Defensive patterns
Strategy: type-guard
Validate before calling
if len(oo) == 0 { return nil, fmt.Errorf("no table found for gvr: %s", gvr) } // already guarded upstream
// verify server table support once per session
tt, ok := oo[0].(*metav1.Table)
if !ok { return nil, fmt.Errorf("gvr %s: got %T, want *metav1.Table", gvr, oo[0]) } Type guard
func isMetaTable(o runtime.Object) bool {
_, ok := o.(*metav1.Table)
return ok
} Try / catch
tbl, err := wl.fetch(ctx, gvr, ns)
if err != nil {
if strings.Contains(err.Error(), "not a metav1.Table") { /* server/proxy not honoring Table accept header */ }
return err
} Prevention
- Confirm the API server supports server-side Table conversion before using the workloads view.
- Remove response-rewriting proxies between k9s and the API server.
- Custom Table DAOs must return *metav1.Table objects.
When it happens
Trigger: An API server or aggregated API that ignores the Table accept header and returns the resource list unconverted; a proxy/stripping proxy rewriting responses; cached/stale results from a custom dao.Table implementation returning non-metav1.Table runtime.Objects.
Common situations: Very old or non-standard Kubernetes distributions; API server behind mutating middlewares; bugs in custom resource views that replace the Table DAO's output; k9s version expecting Table semantics the server does not support.
Related errors
- expected Unstructured, but got %T
- expected Unstructured, but got %T
- expected TableRow, but got %T
- expecting cronjob resource
- expecting Deployment resource
AI-assisted analysis of derailed/k9s@2d3ccc6ba2 (2026-08-15).
Data as JSON: /api/errors/4b5d968bfb72ab7d.
Report an issue: GitHub.