golang/go · critical

internal error: command returned both an error and a WaitFun

Error message

internal error: command returned both an error and a WaitFunc

What it means

Internal error returned by Engine.runCommand when a command's Run returns both a non-nil WaitFunc AND a non-nil error. The contract is exclusive: either Run returns an error (synchronous failure, wait == nil) or it returns a WaitFunc to be awaited later (runErr == nil). Returning both is ambiguous and treated as an implementation bug.

Source

Thrown at src/cmd/internal/script/engine.go:566

func (e *Engine) runCommand(s *State, cmd *command, impl Cmd) error {
	if impl == nil {
		return cmdError(cmd, errors.New("unknown command"))
	}

	async := impl.Usage().Async
	if cmd.background && !async {
		return cmdError(cmd, errors.New("command cannot be run in background"))
	}

	wait, runErr := impl.Run(s, cmd.args...)
	if wait == nil {
		if async && runErr == nil {
			return cmdError(cmd, errors.New("internal error: async command returned a nil WaitFunc"))
		}
		return checkStatus(cmd, runErr)
	}
	if runErr != nil {
		return cmdError(cmd, errors.New("internal error: command returned both an error and a WaitFunc"))
	}

	if cmd.background {
		s.background = append(s.background, backgroundCmd{
			command: cmd,
			wait:    wait,
		})
		// Clear stdout and stderr, since they no longer correspond to the last
		// command executed.
		s.stdout = ""
		s.stderr = ""
		return nil
	}

	stdout, stderr, waitErr := wait(s)
	s.stdout = stdout
	s.stderr = stderr
	if stdout != "" {

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Make Run return either (WaitFunc, nil) on success or (nil, error) on failure — never both non-nil.
  2. On partial failure, either return (nil, err) immediately or capture the error inside the WaitFunc closure and return (waitFn, nil).
  3. Add a unit test asserting the two return values are never simultaneously non-nil.

Example fix

// before
return func(s *script.State) (WaitFunc, error) {
    if bad {
        return waitFn, errors.New("bad") // both non-nil
    }
    return waitFn, nil
}

// after
return func(s *script.State) (WaitFunc, error) {
    if bad {
        return nil, errors.New("bad")
    }
    return waitFn, nil
}
Defensive patterns

Strategy: validation

Validate before calling

// In a custom command, ensure Run never returns both non-nil.
//   if wait != nil && err != nil { err = nil } // or: wait = nil

Type guard

func isBothWaitAndErr(err error) bool {
    var ce *script.CommandError
    return errors.As(err, &ce) && ce.Err != nil &&
        strings.Contains(ce.Err.Error(), "returned both an error and a WaitFunc")
}

Try / catch

// Contract violation; fix the command so it returns (WaitFunc, nil) XOR (nil, error).

Prevention

When it happens

Trigger: A Cmd's Run function returns something like `return waitFn, fmt.Errorf("...")` in the same call. The engine checks `runErr != nil` after confirming `wait != nil` and surfaces the internal error.

Common situations: A custom command author partially fails but still returns the closure, or refactors a sync command to async and leaves an error return on the success path. The typed-nil pitfall can also produce a non-nil error interface unexpectedly.

Related errors


AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12). Data as JSON: /api/errors/5c01ce4a05fe7962. Report an issue: GitHub.