{"record":{"id":"a516f224ab01d002","repo":"derailed/k9s","slug":"spec-to-local-port-count-mismatch-expected-d-but","errorCode":null,"errorMessage":"spec to local port count mismatch. Expected %d but got %d","messagePattern":"spec to local port count mismatch\\. Expected (.+?) but got (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/port/pfs.go","lineNumber":71,"sourceCode":"func ParsePFs(ann string) (PFAnns, error) {\n\tss := strings.Split(ann, \",\")\n\tpp := make(PFAnns, 0, len(ss))\n\tfor _, s := range ss {\n\t\tf, err := ParsePF(s)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tpp = append(pp, f)\n\t}\n\n\treturn pp, nil\n}\n\nfunc ToTunnels(address, specs, localPorts string) (PortTunnels, error) {\n\tpp, lps := strings.Split(specs, \",\"), strings.Split(localPorts, \",\")\n\n\tif len(pp) != len(lps) {\n\t\treturn nil, fmt.Errorf(\"spec to local port count mismatch. Expected %d but got %d\", len(pp), len(lps))\n\t}\n\n\tpts := make(PortTunnels, 0, len(pp))\n\tfor i, p := range pp {\n\t\ta, err := ParsePF(p)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tn, err := a.PortNum()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tpts = append(pts, PortTunnel{\n\t\t\tAddress:       address,\n\t\t\tContainer:     a.Container,\n\t\t\tContainerPort: n,\n\t\t\tLocalPort:     lps[i],\n\t\t})","sourceCodeStart":53,"sourceCodeEnd":89,"githubUrl":"https://github.com/derailed/k9s/blob/2d3ccc6ba2ce98c3781bfc441bb3e884f072774f/internal/port/pfs.go#L53-L89","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before\nspecs=\"a::8080:80,b::9090:81\"  localPorts=\"8080\"\n# after\nspecs=\"a::8080:80,b::9090:81\"  localPorts=\"8080,9090\"","handlingStrategy":"validation","validationCode":"specs := strings.Split(specStr, \",\")\nports := strings.Split(localPortsStr, \",\")\nif len(specs) != len(ports) {\n\treturn fmt.Errorf(\"config drift: %d specs vs %d local ports\", len(specs), len(ports))\n}\n// safe to call port.ToTunnels(address, specStr, localPortsStr)","typeGuard":null,"tryCatchPattern":"Fail fast on the count mismatch — it indicates corrupted config; do not attempt partial tunnel creation from the longer list.","preventionTips":["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"],"tags":["port-forward","configuration","validation"],"backgroundTag":null,"analyzedSha":"2d3ccc6ba2ce98c3781bfc441bb3e884f072774f","analyzedAt":"2026-08-15T16:09:14.432Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}