{"record":{"id":"c6bfa22f53117824","repo":"golang/go","slug":"w-output-pipes-not-closed-after-waiting-v","errorCode":null,"errorMessage":"%w: output pipes not closed after waiting %v","messagePattern":"%w: output pipes not closed after waiting (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/cmd/internal/script/cmds.go","lineNumber":485,"sourceCode":"\t\tif err == nil {\n\t\t\tbreak\n\t\t}\n\t\tif isETXTBSY(err) {\n\t\t\t// If the script (or its host process) just wrote the executable we're\n\t\t\t// trying to run, a fork+exec in another thread may be holding open the FD\n\t\t\t// that we used to write the executable (see https://go.dev/issue/22315).\n\t\t\t// Since the descriptor should have CLOEXEC set, the problem should\n\t\t\t// resolve as soon as the forked child reaches its exec call.\n\t\t\t// Keep retrying until that happens.\n\t\t} else {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\twait := func(s *State) (stdout, stderr string, err error) {\n\t\terr = cmd.Wait()\n\t\tif errors.Is(err, exec.ErrWaitDelay) {\n\t\t\terr = fmt.Errorf(\"%w: output pipes not closed after waiting %v\", err, cmd.WaitDelay)\n\t\t}\n\t\treturn stdoutBuf.String(), stderrBuf.String(), err\n\t}\n\treturn wait, nil\n}\n\n// lookPath is (roughly) like exec.LookPath, but it uses the script's current\n// PATH to find the executable.\nfunc lookPath(s *State, command string) (string, error) {\n\tvar strEqual func(string, string) bool\n\tif runtime.GOOS == \"windows\" || runtime.GOOS == \"darwin\" {\n\t\t// Using GOOS as a proxy for case-insensitive file system.\n\t\t// TODO(bcmills): Remove this assumption.\n\t\tstrEqual = strings.EqualFold\n\t} else {\n\t\tstrEqual = func(a, b string) bool { return a == b }\n\t}\n","sourceCodeStart":467,"sourceCodeEnd":503,"githubUrl":"https://github.com/golang/go/blob/b6b368adc57c96c3151d224d172029f233ead2c3/src/cmd/internal/script/cmds.go#L467-L503","documentation":"Wraps exec.ErrWaitDelay when cmd.Wait times out waiting for output pipes to close after the process has exited. The original Wait error is preserved (%w) and augmented with the configured WaitDelay duration. This indicates the child process exited but left stdout or stderr pipe handles open (typically via a lingering grandchild process inheriting the FDs).","triggerScenarios":"cmd.Wait() returns exec.ErrWaitDelay, meaning the WaitDelay timer expired before the output pipes were fully closed. The child process exited but descendant processes (or the OS) are still holding the pipe file descriptors open.","commonSituations":"The exec'd command spawns child processes that inherit stdout/stderr and outlive the parent. Common with shell pipelines, daemon-spawning commands, or programs that fork without closing inherited FDs. The WaitDelay (configurable on exec.Cmd) provides a grace period; if it expires, this error fires.","solutions":["Increase cmd.WaitDelay to give descendant processes more time to close pipes.","Ensure the exec'd command closes or redirects inherited stdout/stderr (e.g. redirect to /dev/null or a file).","Use process groups (Setpgid) and kill the entire group to clean up lingering descendants.","If using a shell wrapper, add `exec` before the final command or redirect fds: `cmd > /dev/null 2>&1`."],"exampleFix":"// Before: pipes inherited by descendants\n// cmd := exec.Command(\"sh\", \"-c\", \"server & wait\")\n\n// After: redirect so descendants don't hold pipes\n// cmd := exec.Command(\"sh\", \"-c\", \"server >/dev/null 2>&1 & wait\")\n// cmd.WaitDelay = 5 * time.Second","handlingStrategy":"validation","validationCode":"// Set WaitDelay and ensure child processes don't inherit pipes:\ncmd := exec.Command(\"sh\", \"-c\", script)\ncmd.Stdout = &stdoutBuf\ncmd.Stderr = &stderrBuf\ncmd.WaitDelay = 10 * time.Second\n// In the script, redirect inherited fds:\n//   mydaemon >/dev/null 2>&1 &","typeGuard":null,"tryCatchPattern":"// Detect and handle ErrWaitDelay gracefully:\nerr = cmd.Wait()\nif errors.Is(err, exec.ErrWaitDelay) {\n    // Process exited but pipes weren't closed in time.\n    // Output is still available in stdoutBuf/stderrBuf.\n    log.Printf(\"warning: process exited but pipes delayed: %v\", err)\n    err = nil // or treat as non-fatal\n}","preventionTips":["Redirect child process stdout/stderr to /dev/null or files so descendants don't hold the pipes.","Set an appropriate cmd.WaitDelay for commands that spawn background processes.","Use process groups (Setpgid: true) and kill the group to clean up descendants."],"tags":["script","test-framework","exec","wait-delay","pipe-leak","go-compiler-internal"],"backgroundTag":null,"analyzedSha":"b6b368adc57c96c3151d224d172029f233ead2c3","analyzedAt":"2026-08-12T00:22:02.250Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}