{"record":{"id":"77eeb96b40c72098","repo":"derailed/k9s","slug":"expecting-policyres-but-got-t","errorCode":null,"errorMessage":"expecting PolicyRes but got %T","messagePattern":"expecting PolicyRes but got %T","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/render/rbac.go","lineNumber":61,"sourceCode":"}\n\n// Header returns a header row.\nfunc (Rbac) Header(string) model1.Header {\n\th := make(model1.Header, 0, 10)\n\th = append(h,\n\t\tmodel1.HeaderColumn{Name: \"NAME\"},\n\t\tmodel1.HeaderColumn{Name: \"API-GROUP\"},\n\t)\n\th = append(h, rbacVerbHeader()...)\n\n\treturn append(h, model1.HeaderColumn{Name: \"VALID\", Attrs: model1.Attrs{Wide: true}})\n}\n\n// Render renders a K8s resource to screen.\nfunc (r Rbac) Render(o any, ns string, ro *model1.Row) error {\n\tp, ok := o.(*PolicyRes)\n\tif !ok {\n\t\treturn fmt.Errorf(\"expecting PolicyRes but got %T\", o)\n\t}\n\n\tro.ID = p.Resource\n\tro.Fields = make(model1.Fields, 0, len(r.Header(ns)))\n\tro.Fields = append(ro.Fields,\n\t\tcleanseResource(p.Resource),\n\t\tp.Group,\n\t)\n\tro.Fields = append(ro.Fields, asVerbs(p.Verbs)...)\n\tro.Fields = append(ro.Fields, \"\")\n\n\treturn nil\n}\n\n// ----------------------------------------------------------------------------\n// Helpers...\n\nfunc asVerbs(verbs []string) []string {","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/derailed/k9s/blob/2d3ccc6ba2ce98c3781bfc441bb3e884f072774f/internal/render/rbac.go#L43-L79","documentation":"Rbac.Render draws the RBAC policy browser rows and requires a *render.PolicyRes — the synthetic row type built by NewPolicyRes(ns, binding, res, group, verbs) when k9s flattens ClusterRoleBindings and RoleBindings into policy rows. The comma-ok assertion is on the pointer type, so a value PolicyRes or any other object fails with this error, which prints the concrete %T received.","triggerScenarios":"Calling Rbac{}.Render with render.PolicyRes (a value, missing &) instead of *render.PolicyRes; feeding pods or unstructured resources into the RBAC view; a custom view reusing the Rbac renderer for non-policy rows.","commonSituations":"Extending the RBAC views or writing plugins that aggregate authorizations; refactoring the policy collector so it emits PolicyRes values instead of pointers; test fixtures constructed as values.","solutions":["Always pass policies as *render.PolicyRes (the Policies collection already stores pointers)","Keep the Rbac renderer bound only to the policy/verbs views","Log the %T from the error to locate the producer emitting the wrong type","Use a comma-ok check at custom call sites and skip invalid rows"],"exampleFix":"// before\nerr := rbacRenderer.Render(render.PolicyRes{Resource: \"pods\"}, ns, row)\n// after\nerr := rbacRenderer.Render(&render.PolicyRes{Resource: \"pods\"}, ns, row)","handlingStrategy":"type-guard","validationCode":"// gate before invoking the rbac renderer\nif _, ok := o.(*render.PolicyRes); !ok {\n\treturn fmt.Errorf(\"rbac view requires *render.PolicyRes, got %T\", o)\n}\nerr := rbacRenderer.Render(o, ns, row)","typeGuard":"func isPolicyRes(o any) bool {\n\t_, ok := o.(*render.PolicyRes)\n\treturn ok\n}","tryCatchPattern":"if err := rbacRenderer.Render(o, ns, row); err != nil {\n\tslog.Warn(\"rbac row skipped\", \"type\", fmt.Sprintf(\"%T\", o), slogs.Error, err)\n\treturn nil // drop the row, keep the view alive\n}","preventionTips":["Remember Rbac.Render asserts the pointer: pass *render.PolicyRes, never the value","Build rows with NewPolicyRes so the shape stays correct across refactors","Bind the Rbac renderer only to policy/verbs views","Use comma-ok assertions at custom call sites"],"tags":["kubernetes","go","type-assertion","rbac","renderer","k9s"],"backgroundTag":null,"analyzedSha":"2d3ccc6ba2ce98c3781bfc441bb3e884f072774f","analyzedAt":"2026-08-15T16:09:14.432Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}