GoogleContainerTools/skaffold · error
Forward() called before kubectl forwarder was started
Error message
Forward() called before kubectl forwarder was started
What it means
KubectlForwarder.forward checks an atomic `started` flag before starting a port-forward loop; if Forward() was called before the forwarder's Start() routine was launched, the kubectl process plumbing (base command, kill channel) is not initialized, so it pushes this error onto errChan instead of panicking. It is a lifecycle/ordering guard for the forwarder's internal state machine.
Source
Thrown at pkg/skaffold/kubernetes/portforward/kubectl_forwarder.go:102
l := log.Entry(parentCtx)
resourceName := ""
if pfe != nil {
resourceName = pfe.resource.Name
}
l.Tracef("KubectlForwarder.Forward(%s): waiting on errChan", resourceName)
select {
case <-parentCtx.Done():
l.Tracef("KubectlForwarder.Forward(%s): parentCtx canceled, returning nil error", resourceName)
return nil
case err := <-errChan:
l.Tracef("KubectlForwarder.Forward(%s): got error on errChan, returning: %+v", resourceName, err)
return err
}
}
func (k *KubectlForwarder) forward(ctx context.Context, pfe *portForwardEntry, errChan chan error) {
if atomic.LoadInt32(&k.started) == 0 {
errChan <- fmt.Errorf("Forward() called before kubectl forwarder was started")
return
}
var notifiedUser bool
defer deferFunc()
for {
pfe.terminationLock.Lock()
if pfe.terminated {
log.Entry(ctx).Debugf("port forwarding %v was cancelled...", pfe)
pfe.terminationLock.Unlock()
errChan <- nil
return
}
pfe.terminationLock.Unlock()
if !isPortFree(util.Loopback, pfe.localPort) {
// Assuming that Skaffold brokered ports don't overlap, this has to be an external process that started
// since the dev loop kicked off. We are notifying the user in the hope that they can fix itView on GitHub (pinned to a1189de023)
Solutions
- Call the forwarder's Start()/Run method before invoking Forward()
- Ensure port-forward entries are only submitted after the forwarder signals readiness
- In tests, construct the forwarder the same way production code does, including the Start step
Example fix
// before fwd := NewKubectlForwarder(out, entrypoints) fwd.Forward(ctx, entry) // panics/errors: not started // after fwd := NewKubectlForwarder(out, entrypoints) go fwd.Run(ctx, kubectl) // wait for readiness, then: fwd.Forward(ctx, entry)
Defensive patterns
Strategy: validation
Validate before calling
// only submit port-forward entries after the forwarder is running
select {
case <-forwarderReady: // channel closed once Run/Start has begun
forwarder.Forward(ctx, entry)
case <-time.After(5 * time.Second):
log.Fatal("kubectl forwarder did not start in time")
} Try / catch
errChan := make(chan error, 1)
go func() { forwarder.Forward(ctx, entry) }()
select {
case err := <-errChan:
if strings.Contains(err.Error(), "called before kubectl forwarder was started") {
// re-initialize: start forwarder, then re-Forward
}
case <-ctx.Done():
} Prevention
- Always call Start()/Run on the forwarder before queuing Forward calls
- Initialize forwarders early in application startup, before port-forward entries are generated
- In tests, mirror the production initialization sequence
When it happens
Trigger: Calling Forward() on a KubectlForwarder instance without having called its Start() method first (or before the started goroutine has set the started flag), then supplying a portForwardEntry.
Common situations: Embedding KubectlForwarder in custom tooling and forgetting to call Start(); calling Forward() immediately after construction in tests; race where port-forward entries are queued before the forwarder's Run loop starts.
Related errors
- port forwarding %v got terminated: output: %s
- c.Message (pod status condition message)
- unable to lookup minikube executable. Please add it to PATH
- invalid local-registry-hosting ConfigMap
- strings.Join(errMsgs, "\n") (joined helm cleanup error messa
AI-assisted analysis of GoogleContainerTools/skaffold@a1189de023 (2026-09-05).
Data as JSON: /api/errors/26e091696c9c93ac.
Report an issue: GitHub.