derailed/k9s · error
spec to local port count mismatch. Expected %d but got %d
Error message
spec to local port count mismatch. Expected %d but got %d
What it means
Package-level port.ToTunnels(address, specs, localPorts) splits the comma-joined spec string and the comma-joined local-ports string and requires equal counts. The error is an internal consistency check: every port-forward spec must have exactly one resolved local port.
Source
Thrown at internal/port/pfs.go:71
func ParsePFs(ann string) (PFAnns, error) {
ss := strings.Split(ann, ",")
pp := make(PFAnns, 0, len(ss))
for _, s := range ss {
f, err := ParsePF(s)
if err != nil {
return nil, err
}
pp = append(pp, f)
}
return pp, nil
}
func ToTunnels(address, specs, localPorts string) (PortTunnels, error) {
pp, lps := strings.Split(specs, ","), strings.Split(localPorts, ",")
if len(pp) != len(lps) {
return nil, fmt.Errorf("spec to local port count mismatch. Expected %d but got %d", len(pp), len(lps))
}
pts := make(PortTunnels, 0, len(pp))
for i, p := range pp {
a, err := ParsePF(p)
if err != nil {
return nil, err
}
n, err := a.PortNum()
if err != nil {
return nil, err
}
pts = append(pts, PortTunnel{
Address: address,
Container: a.Container,
ContainerPort: n,
LocalPort: lps[i],
})View on GitHub (pinned to 2d3ccc6ba2)
Solutions
- Make the lists 1:1 — add the missing local port or remove the orphaned spec entry
- Count entries: echo "$specs" | tr ',' '\n' | wc -l vs the same for localPorts
- Re-enable auto port assignment so k9s regenerates a matched pair instead of hand-maintaining it
- Restart k9s after fixing so tunnels are rebuilt from the corrected config
Example fix
# before specs="a::8080:80,b::9090:81" localPorts="8080" # after specs="a::8080:80,b::9090:81" localPorts="8080,9090"
Defensive patterns
Strategy: validation
Validate before calling
specs := strings.Split(specStr, ",")
ports := strings.Split(localPortsStr, ",")
if len(specs) != len(ports) {
return fmt.Errorf("config drift: %d specs vs %d local ports", len(specs), len(ports))
}
// safe to call port.ToTunnels(address, specStr, localPortsStr) Try / catch
Fail fast on the count mismatch — it indicates corrupted config; do not attempt partial tunnel creation from the longer list.
Prevention
- Never hand-edit one list without the other; regenerate both from PFAnns
- Let k9s auto-assign local ports instead of maintaining parallel comma lists
- Validate config pairs in CI with a small script comparing counts
When it happens
Trigger: specs='a::8080:80,b::9090:81' with localPorts='8080' (2 specs vs 1 local port). Occurs when the serialized spec list and the auto-assigned/manual local port list get out of sync, e.g. one annotation failed to resolve a port number before serialization.
Common situations: Hand-editing k9s config portForward sections and adding a spec without a matching port; partial serialization when a named port cannot be matched to a container port; merging configs across machines.
Related errors
- invalid annotation %q
- pod must be running. Current status=%v
- port-forward is already active on pod %s
- no exposed ports
- no port number assigned
AI-assisted analysis of derailed/k9s@2d3ccc6ba2 (2026-08-15).
Data as JSON: /api/errors/a516f224ab01d002.
Report an issue: GitHub.