From c8359d859842d14cd37b375c813d38642f361aca Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Mon, 25 May 2026 17:27:29 +0300 Subject: [PATCH] fix(agent/agentproc): read process info before output to prevent TOCTOU (#25646) handleProcessOutput read proc.output() then proc.info() using separate locks. Between the two reads the exit goroutine could finish I/O and set running=false, pairing stale output with final status. On Windows CI this caused OutputExceedsBuffer to flake when the buffer snapshot caught mid-write data (OmittedBytes=0) but info reported the process as exited. Swap the read order so info is read first. The exit goroutine completes cmd.Wait (draining all pipe data) before setting running=false, so seeing Running=false guarantees the subsequent output read reflects the final buffer state. Closes CODAGT-399 --- agent/agentproc/api.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/agent/agentproc/api.go b/agent/agentproc/api.go index 4dcc07b541..8b8e1ce2ec 100644 --- a/agent/agentproc/api.go +++ b/agent/agentproc/api.go @@ -200,8 +200,13 @@ func (api *API) handleProcessOutput(rw http.ResponseWriter, r *http.Request) { // Fall through to read snapshot below. } - output, truncated := proc.output() + // Read info before output to avoid a TOCTOU race. The exit + // goroutine completes all buffer writes (cmd.Wait) before + // setting running=false, so if info reports the process as + // exited, the subsequent output read is guaranteed to reflect + // the final buffer state. info := proc.info() + output, truncated := proc.output() httpapi.Write(ctx, rw, http.StatusOK, workspacesdk.ProcessOutputResponse{ Output: output,