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
- Make Run return either (WaitFunc, nil) on success or (nil, error) on failure — never both non-nil.
- On partial failure, either return (nil, err) immediately or capture the error inside the WaitFunc closure and return (waitFn, nil).
- 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
- Return either a WaitFunc or an error from Run — never both.
- On partial failure, capture the error inside the WaitFunc.
- Test the two return values are never simultaneously non-nil.
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
- internal error: async command returned a nil WaitFunc
- destination is not a directory
- no engine configured
- duplicated '!' or '?' token
- empty condition
AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12).
Data as JSON: /api/errors/5c01ce4a05fe7962.
Report an issue: GitHub.