derailed/k9s · error

invalid port-forward specification %s

Error message

invalid port-forward specification %s

What it means

Thrown by port.ParsePF when the annotation matches neither the plain form nor the full form regex \A([\w-]+)::(\d*):(\d*|[\w-]*)/?(\d+)?\z, i.e. 'container::localPort:containerPort[/portNum]'. It is the generic 'not a port-forward spec k9s understands' error.

Source

Thrown at internal/port/pf.go:67

		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) {
	if pf, err := ParsePlainPF(ann); err == nil {
		return pf, nil
	}
	var pf PFAnn
	if mm := pfPlainRX.FindStringSubmatch(strings.TrimSpace(ann)); len(mm) == 3 {
		pf.containerPortNum = mm[0]
	}
	r := pfRX.FindStringSubmatch(strings.TrimSpace(ann))
	if len(r) < 4 {
		return &pf, fmt.Errorf("invalid port-forward specification %s", ann)
	}
	pf.Container = r[1]
	pf.LocalPort, pf.ContainerPort = r[2], intstr.Parse(r[3])
	if r[3] == "" {
		pf.ContainerPort = intstr.Parse(pf.LocalPort)
	}

	// Testing only!
	if len(r) == 5 && r[4] != "" {
		pf.containerPortNum = r[4]
	}
	if pf.LocalPort == "" {
		pf.LocalPort = pf.containerPortNum
	}

	return &pf, nil
}

View on GitHub (pinned to 2d3ccc6ba2)

Solutions

  1. Use the full form 'container::localPort:containerPort', e.g. 'mysvc::8080:9090'
  2. Keep the container segment to letters, digits, underscore and hyphen only (no '/', '.', ':')
  3. Keep the local port numeric; named ports are only valid for the container port
  4. Print the raw annotation (kubectl get <res> -o jsonpath='{.metadata.annotations.k9scli\.io/port-forwards}') and eyeball it against the two accepted forms

Example fix

# before
k9scli.io/port-forwards: "mysvc:8080:9090"
# after
k9scli.io/port-forwards: "mysvc::8080:9090"
Defensive patterns

Strategy: validation

Validate before calling

var (
	plainPFRX = regexp.MustCompile(`\A(\d*):(\d*|[\w-]*)\z`)
	fullPFRX  = regexp.MustCompile(`\A([\w-]+)::(\d*):(\d*|[\w-]*)/?(\d+)?\z`)
)

func validPF(ann string) bool {
	s := strings.TrimSpace(ann)
	return plainPFRX.MatchString(s) || fullPFRX.MatchString(s)
}

Try / catch

On error from port.ParsePF, wrap with context (fmt.Errorf("parsing port-forward annotation %q: %w", ann, err)) and surface to the user as a config hint; do not retry — the string is deterministic.

Prevention

When it happens

Trigger: Values like 'svc:8080:9090' (single colon before container segment), '::8080:80' (empty container, [\w-]+ requires at least one character), 'ns/svc::8080' (slash in container name), or '8080:80:90' (three colon-separated fields with no '::').

Common situations: Typos between ':' and '::' in hand-written annotations; copying 'namespace/service:port' kubectl syntax; k9s version changes that tightened the accepted grammar; annotation placed under the wrong key so unrelated text is parsed.

Related errors


AI-assisted analysis of derailed/k9s@2d3ccc6ba2 (2026-08-15). Data as JSON: /api/errors/df1bf37aef9e22e0. Report an issue: GitHub.