derailed/k9s · warning
port-forward is already active on pod %s
Error message
port-forward is already active on pod %s
What it means
startFwdCB (internal/view/pf_extender.go:156) checks the factory's active forwarder registry keyed by dao.PortForwardID(path, container, PortMap()) before starting each tunnel. If a forwarder is already registered for the same pod path + container + container/local port mapping, it refuses to start a duplicate and reports the pod path.
Source
Thrown at internal/view/pf_extender.go:156
pf.SetActive(true)
if err := f.ForwardPorts(); err != nil {
v.App().Flash().Warnf("PortForward failed for %s: %s. Deleting!", pf.ID(), err)
}
v.App().QueueUpdateDraw(func() {
v.App().factory.DeleteForwarder(pf.ID())
pf.SetActive(false)
})
}
func startFwdCB(v ResourceViewer, path string, pts port.PortTunnels) error {
if err := pts.CheckAvailable(context.Background()); err != nil {
return err
}
tt := make([]string, 0, len(pts))
for _, pt := range pts {
if _, ok := v.App().factory.ForwarderFor(dao.PortForwardID(path, pt.Container, pt.PortMap())); ok {
return fmt.Errorf("port-forward is already active on pod %s", path)
}
pf := dao.NewPortForwarder(v.App().factory)
fwd, err := pf.Start(path, pt)
if err != nil {
return err
}
slog.Debug(">>> Starting port forward",
slogs.PFID, pf.ID(),
slogs.PFTunnel, pt,
)
go runForward(v, pf, fwd)
tt = append(tt, pt.LocalPort)
}
if len(tt) == 1 {
v.App().Flash().Infof("PortForward activated %s", tt[0])
return nil
}
v.App().Flash().Infof("PortForwards activated %s", strings.Join(tt, ","))View on GitHub (pinned to 2d3ccc6ba2)
Solutions
- Open the :pf / portforwards view and stop the existing tunnel, then start the new one
- Reuse the existing tunnel instead of creating a duplicate — just curl the already-forwarded local port
- Enter a different local port in the dialog so PortForwardID differs
- If the tunnel belongs to a stale k9s session, exit that session (or kill the kubectl port-forward process) and retry
Example fix
// before: start same mapping twice // Container Port: web::8080 Local Port: 8080 (already active) // -> port-forward is already active on pod ns1/mypod # after: stop it first, or map a new local port # in k9s: :pf -> select -> <delete> OR # Container Port: web::8080 Local Port: 8081
Defensive patterns
Strategy: validation
Validate before calling
// check for an existing forwarder before starting
for _, pt := range pts {
if _, ok := factory.ForwarderFor(dao.PortForwardID(path, pt.Container, pt.PortMap())); ok {
return fmt.Errorf("tunnel for %s/%s already exists; stop it or pick another local port", path, pt.Container)
}
} Prevention
- Review the :pf (portforwards) view before creating new tunnels
- Prefer distinct local ports per tunnel so PortForwardID never collides
- Exit k9s cleanly so forwarders are torn down instead of lingering
When it happens
Trigger: Opening the port-forward dialog on the same pod twice and re-entering an identical container::port -> local port mapping; forwarding again after switching away from the portforwards view without first stopping the existing tunnel.
Common situations: Users forget an earlier tunnel is still live; tunnel was started in a different k9s session/tab still running; the local port mysteriously busy because the existing forwarder already binds it.
Related errors
- pod must be running. Current status=%v
- k9s config file %q load failed: %w
- duplicate input name %q
- default value %q for input %q is not a valid option
- default value %q for bool input %q must be "true" or "false"
AI-assisted analysis of derailed/k9s@2d3ccc6ba2 (2026-08-15).
Data as JSON: /api/errors/5259a9d5f4a0af45.
Report an issue: GitHub.