derailed/k9s · error
invalid annotation %q
Error message
invalid annotation %q
What it means
ParsePlainPF parses the k9s port-forward annotation shorthand (plain form: 'port' or 'local:remote' / 'port:name', full form 'container::local:remote'). It rejects an annotation value that is empty before any regex matching, because an empty string cannot carry a port pair.
Source
Thrown at internal/port/pf.go:39
)
var (
pfRX = regexp.MustCompile(`\A([\w-]+)::(\d*):?(\d*|[\w-]*)/?(\d+)?\z`)
pfPlainRX = regexp.MustCompile(`\A(\d*):?(\d*|[\w-]*)\z`)
)
// PFAnn represents a portforward annotation value.
// Shape: container/portname|portNum:localPort
type PFAnn struct {
Container string
ContainerPort intstr.IntOrString
LocalPort string
containerPortNum string
}
func ParsePlainPF(ann string) (*PFAnn, error) {
if ann == "" {
return nil, fmt.Errorf("invalid annotation %q", ann)
}
var pf PFAnn
mm := pfPlainRX.FindStringSubmatch(strings.TrimSpace(ann))
if len(mm) < 3 {
return nil, fmt.Errorf("invalid plain port-forward %s", ann)
}
if mm[2] == "" {
pf.ContainerPort = intstr.Parse(mm[1])
pf.LocalPort = mm[1]
return &pf, nil
}
pf.LocalPort, pf.ContainerPort = mm[1], intstr.Parse(mm[2])
return &pf, nil
}
// ParsePF hydrate a portforward annotation from string.
func ParsePF(ann string) (*PFAnn, error) {View on GitHub (pinned to 2d3ccc6ba2)
Solutions
- Remove the empty annotation: kubectl annotate pod <name> k9scli.io/port-forwards-
- Or set a valid value: plain '8080' (same local/remote), '9090:8080' (local:remote), or 'container::9090:8080'
- In Helm templates, guard with {{- if .Values.pf }} so the annotation is only emitted when non-empty
Example fix
# before: empty annotation
metadata:
annotations:
k9scli.io/port-forwards: ""
# after: either omit the key or set a valid value
metadata:
annotations:
k9scli.io/port-forwards: "9090:8080" Defensive patterns
Strategy: validation
Validate before calling
// Skip empty annotation values before parsing.
for _, key := range []string{port.K9sPortForwardsKey, port.K9sAutoPortForwardsKey} {
v := strings.TrimSpace(anns[key])
if anns[key] != "" && v == "" {
return fmt.Errorf("annotation %s is whitespace-only; remove it or set e.g. '9090:8080'", key)
}
if v == "" { continue }
pf, err := port.ParsePlainPF(v)
...
} Type guard
func validPFShape(v string) bool {
v = strings.TrimSpace(v)
if v == "" { return false }
mm := regexp.MustCompile(`\A(\d*):?(\d*|[\w-]*)\z`).FindStringSubmatch(v)
return len(mm) >= 3 && mm[0] == v
} Try / catch
pf, err := port.ParsePlainPF(ann)
if err != nil {
if strings.Contains(err.Error(), "invalid annotation") {
// tolerate and skip: an empty annotation must not break port-forward discovery
slog.Warn("ignoring empty k9s port-forward annotation", slogs.GVR, gvr)
continue
}
return err
} Prevention
- In Helm/manifest templates, emit the k9s annotation only when the value is set ({{- if }})
- Validate annotation values in CI linting: k9scli.io/port-forwards must match port, local:remote, or container::local:remote
When it happens
Trigger: A pod/service carries the k9scli.io/port-forwards or k9scli.io/auto-port-forwards annotation but its value is '' (or whitespace-only after trimming in the regex path): e.g. `k9scli.io/port-forwards: ""` set by a template variable that rendered empty.
Common situations: Helm templates defaulting the annotation to an empty string instead of omitting it; CI pipelines injecting the annotation key unconditionally; manual copy-paste leaving the value blank.
Related errors
- no exposed ports
- no port number assigned
- no tree renderer defined for this resource
- no data found for resource %s
- invalid plain port-forward %s
AI-assisted analysis of derailed/k9s@2d3ccc6ba2 (2026-08-15).
Data as JSON: /api/errors/8ee579bb4208e473.
Report an issue: GitHub.