processownership
Checks that started os/exec commands are waited on or transferred to a wait owner.
What it detects
Tracks commands successfully started with os/exec and reports return paths
that neither wait for the process nor transfer the command to code that owns
waiting for it.
Launching a process without waiting is not reported on its own: opening a browser or relaunching a daemon may intentionally outlive the caller. The check reports a command that is waited on or handed on along some return paths but not others. Commands built by helpers or reached through callbacks are not checked.
The full list of precision boundaries, with the fixtures that pin each one, is in the processownership design notes.
Checks
| Check | Tier | Kind | What it detects |
|---|---|---|---|
missing-wait |
core | defect | Reports successfully started commands that are neither waited on nor transferred. |
Why this is flagged
Starting a process without waiting for it can leave operating-system resources unreleased and loses the process’s final error or exit status. Waiting gives the child process a complete, observable lifecycle.
Further reading: exec.Cmd.Wait.
How to fix it
Use Run when the program can wait immediately. If it must use Start, make
sure every successful start is followed by Wait, and handle the resulting
exit status or error.
Examples
Flagged code
func run(ctx context.Context, wait bool) error { command := exec.CommandContext(ctx, "worker") // gohawk: started command is not waited on every successful return path if err := command.Start(); err != nil { return err } if wait { return command.Wait() } return nil}Accepted code
func runSafely(ctx context.Context) error { command := exec.CommandContext(ctx, "worker") if err := command.Start(); err != nil { return err } return command.Wait()}