handleProcessOutput returns the output of a process.
(rw http.ResponseWriter, r *http.Request)
| 150 | |
| 151 | // handleProcessOutput returns the output of a process. |
| 152 | func (api *API) handleProcessOutput(rw http.ResponseWriter, r *http.Request) { |
| 153 | ctx := r.Context() |
| 154 | logger := api.logger.With(agentchat.Fields(ctx)...) |
| 155 | |
| 156 | id := chi.URLParam(r, "id") |
| 157 | proc, ok := api.manager.get(id) |
| 158 | if !ok { |
| 159 | httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{ |
| 160 | Message: fmt.Sprintf("Process %q not found.", id), |
| 161 | }) |
| 162 | return |
| 163 | } |
| 164 | |
| 165 | // Enforce chat ID isolation. If the request carries |
| 166 | // a chat context, only allow access to processes |
| 167 | // belonging to that chat. |
| 168 | if chatContext, ok := agentchat.FromContext(ctx); ok { |
| 169 | if proc.chatID != "" && proc.chatID != chatContext.ID.String() { |
| 170 | httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{ |
| 171 | Message: fmt.Sprintf("Process %q not found.", id), |
| 172 | }) |
| 173 | return |
| 174 | } |
| 175 | } |
| 176 | |
| 177 | // Check for blocking mode via query params. |
| 178 | waitStr := r.URL.Query().Get("wait") |
| 179 | wantWait := waitStr == "true" |
| 180 | |
| 181 | if wantWait { |
| 182 | // Extend the write deadline so the HTTP server's |
| 183 | // WriteTimeout does not kill the connection while |
| 184 | // we block. |
| 185 | rc := http.NewResponseController(rw) |
| 186 | // Add headroom beyond the wait timeout so there's time to |
| 187 | // write the response after the blocking wait completes. |
| 188 | if err := rc.SetWriteDeadline(time.Now().Add(maxWaitDuration + 30*time.Second)); err != nil { |
| 189 | logger.Error(ctx, "extend write deadline for blocking process output", |
| 190 | slog.Error(err), |
| 191 | ) |
| 192 | } |
| 193 | |
| 194 | // Cap the wait at maxWaitDuration regardless of |
| 195 | // client-supplied timeout. |
| 196 | waitCtx, waitCancel := context.WithTimeout(ctx, maxWaitDuration) |
| 197 | defer waitCancel() |
| 198 | |
| 199 | _ = proc.waitForOutput(waitCtx) |
| 200 | // Fall through to read snapshot below. |
| 201 | } |
| 202 | |
| 203 | // Read info before output to avoid a TOCTOU race. The exit |
| 204 | // goroutine completes all buffer writes (cmd.Wait) before |
| 205 | // setting running=false, so if info reports the process as |
| 206 | // exited, the subsequent output read is guaranteed to reflect |
| 207 | // the final buffer state. |
| 208 | info := proc.info() |
| 209 | output, truncated := proc.output() |
nothing calls this directly
no test coverage detected