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

  1. Open the :pf / portforwards view and stop the existing tunnel, then start the new one
  2. Reuse the existing tunnel instead of creating a duplicate — just curl the already-forwarded local port
  3. Enter a different local port in the dialog so PortForwardID differs
  4. 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

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


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