Context, Concurrency, and Processes
- Thread
context.Contextthrough long-running, subprocess, and networked work; preferexec.CommandContext; use derived contexts and timeouts for cleanup and HTTP calls. - Route every long-lived subprocess spawned for a cancellable step or agent invocation through
shellenv.ConfigureShellCommand(cmd): it creates a process-tree boundary and installscmd.Cancelto kill the whole tree, so grandchildren (test workers, build watchers) cannot outlive cancellation and hold the next run's worktree locked. cmd.Cancelcovers only cancellation; on clean exit or error the group is not reaped, and leaked grandchildren accumulate until the OS OOM-kills the daemon (surfacing asdaemon crashed during executionwith no stack trace). Useshellenv.RunShellCommand/OutputShellCommand/CombinedOutputShellCommandfor one-shot commands, orStartShellCommandplusTerminateShellCommandGroupwhen handling pipes manually; the helper doc comments ininternal/shellenvown the details.ConfigureShellCommandalso installs a 5scmd.WaitDelaybackstop so a grandchild holding an inherited pipe cannot wedgecmd.Waitforever. Regressions:TestCodexAgent_Run_ReapsLeakedGrandchildOnCleanExit,TestRunShellCommandWithEnv_ReapsGrandchildOnCleanExit,TestTerminateShellCommandGroup_*.- A process group is a lineage container, not a sandbox: a descendant that calls
setsid(2)/setpgid(2)(agent CLIs sandboxing their tool runners, any daemonizing worker script) leaves the group, and after its parent exits nothing lineage-based can name it again - it burns CPU and holds a deleted worktree's cwd forever.internal/procreapis the identity-based backstop: it matches a process by the run worktree its cwd resolves under (deliberately never argv, which a legitimategit worktree removealso carries), never touches pid<=1/itself/its ancestors, spares worktrees whose run is still pending or running, and escalates SIGTERM to SIGKILL only after a grace period. Reach is<NM_HOME>/worktreesby path shape plus exactly the run worktrees a caller names from run records (Options.Worktrees), never a configured worktree root by shape - an operator's own directory is unmatchable unless a run row names it. Every site that removes a run worktree sweeps it first throughprocreap.SweepRunWorktree(s)(run cleanup and setup failure viaRunManager.removeRunWorktree, startup cleanup, eject), scoped and therefore without age floor or run-active check; the unscoped startup sweep inrecoverOnStartupkeeps theorphanProcessMinAgefloor. All best effort. Windows needs none of this - job objects contain the whole tree - so the platform layer reports an empty table. Regressions:internal/procreap,TestSweepOrphanRunProcessesReapsFinishedRunAndSparesActiveOne,TestSweepRunWorktreeProcessesReapsLeakedChildAtRunCleanup,TestTerminateShellCommandGroup_AsksBeforeKilling,TestTerminateShellCommandGroup_EscalatesWhenSIGTERMIsIgnored. - On Windows the daemon runs console-less, so route every console child through
winproc.Harden(cmd)(no-op elsewhere, idempotent, preserves existing creation flags) or a console window flashes per child (#287).shellenv.ConfigureShellCommandalready calls it; one-shot commands built directly must call it themselves. Regressions:TestHarden*ininternal/winproc. - Protect shared mutable state with the standard sync/atomic tools, and be explicit about ownership and cleanup of goroutines, worktrees, temp dirs, and channels.