Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion lib/httpapi/events_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
st "github.com/coder/agentapi/lib/screentracker"
"github.com/coder/quartz"
"github.com/stretchr/testify/assert"
st "github.com/coder/agentapi/lib/screentracker"
)

// Traces to: FR-HTTP-008
Expand Down
11 changes: 9 additions & 2 deletions lib/httpapi/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type MessageType string

const (
MessageTypeUser MessageType = "user"
MessageTypeRaw MessageType = "raw"
MessageTypeRaw MessageType = "raw"
MessageTypeCommand MessageType = "command"
)

Expand Down Expand Up @@ -81,6 +81,13 @@ type HealthResponse struct {
}
}

// VersionResponse represents the server version response.
type VersionResponse struct {
Body struct {
Version string `json:"version" doc:"AgentAPI version"`
}
}

// ReadyResponse represents the readiness check response
type ReadyResponse struct {
Body struct {
Expand All @@ -101,7 +108,7 @@ type StatusResponse struct {
type InfoResponse struct {
Body struct {
Version string `json:"version" doc:"AgentAPI version"`
AgentType mf.AgentType `json:"agent_type" doc:"Type of the agent being used by the server."`
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Transport field never set in status handler

High Severity

The removed getStatus in server.go set resp.Body.Transport = s.transport, but the surviving implementation in handlers.go omits this assignment. Since StatusResponse still declares a Transport field, the /status endpoint now always returns an empty string for transport instead of the actual value ("pty" or "acp"). Clients relying on this field to determine the backend transport will break.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7652f7e. Configure here.

AgentType mf.AgentType `json:"agent_type" doc:"Type of the agent being used by the server."`
Features map[string]bool `json:"features" doc:"Supported features"`
}
}
Expand Down
179 changes: 0 additions & 179 deletions lib/httpapi/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -340,185 +340,6 @@ func (s *Server) registerRoutes() {
s.registerStaticFileRoutes()
}

// getStatus handles GET /status
func (s *Server) getStatus(ctx context.Context, input *struct{}) (*StatusResponse, error) {
s.mu.RLock()
defer s.mu.RUnlock()

status := s.conversation.Status()
agentStatus := convertStatus(status)

resp := &StatusResponse{}
resp.Body.Status = agentStatus
resp.Body.AgentType = s.agentType
resp.Body.Transport = s.transport

return resp, nil
}

// getMessages handles GET /messages
func (s *Server) getMessages(ctx context.Context, input *struct{}) (*MessagesResponse, error) {
s.mu.RLock()
defer s.mu.RUnlock()

resp := &MessagesResponse{}
resp.Body.Messages = make([]Message, len(s.conversation.Messages()))
for i, msg := range s.conversation.Messages() {
resp.Body.Messages[i] = Message{
Id: msg.Id,
Role: msg.Role,
Content: msg.Message,
Time: msg.Time,
}
}

return resp, nil
}

// createMessage handles POST /message
func (s *Server) createMessage(ctx context.Context, input *MessageRequest) (*MessageResponse, error) {
s.mu.Lock()
defer s.mu.Unlock()

switch input.Body.Type {
case MessageTypeUser:
if err := s.conversation.Send(FormatMessage(s.agentType, input.Body.Content)...); err != nil {
return nil, xerrors.Errorf("failed to send message: %w", err)
}
case MessageTypeRaw:
if _, err := s.agentio.Write([]byte(input.Body.Content)); err != nil {
return nil, xerrors.Errorf("failed to send message: %w", err)
}
}

resp := &MessageResponse{}
resp.Body.Ok = true

return resp, nil
}

// uploadFiles handles POST /upload
func (s *Server) uploadFiles(ctx context.Context, input *struct {
RawBody huma.MultipartFormFiles[UploadRequest]
},
) (*UploadResponse, error) {
formData := input.RawBody.Data()

file := formData.File.File

// Limit file size to 10MB
const maxFileSize = 10 << 20 // 10MB
buf, err := io.ReadAll(io.LimitReader(file, maxFileSize+1))
if err != nil {
return nil, xerrors.Errorf("failed to upload file: %w", err)
}
if len(buf) > maxFileSize {
return nil, huma.Error400BadRequest("file size exceeds 10MB limit")
}

// Calculate checksum of the uploaded file to create unique subdirectory
hash := sha256.Sum256(buf)
checksum := hex.EncodeToString(hash[:8]) // Use first 8 bytes (16 hex chars)

// Create checksum-based subdirectory in tempDir
uploadDir := filepath.Join(s.tempDir, checksum)
err = os.MkdirAll(uploadDir, 0o755)
if err != nil {
return nil, xerrors.Errorf("failed to create upload directory: %w", err)
}

// Save individual file with original filename (extract just the base filename for security)
filename := filepath.Base(formData.File.Filename)

outPath := filepath.Join(uploadDir, filename)
err = os.WriteFile(outPath, buf, 0o644)
if err != nil {
return nil, xerrors.Errorf("failed to write file: %w", err)
}

resp := &UploadResponse{}
resp.Body.Ok = true
resp.Body.FilePath = outPath
return resp, nil
}

// subscribeEvents is an SSE endpoint that sends events to the client
func (s *Server) subscribeEvents(ctx context.Context, input *struct{}, send sse.Sender) {
subscriberId, ch, stateEvents := s.emitter.Subscribe()
defer s.emitter.Unsubscribe(subscriberId)

s.logger.Info("New subscriber", "subscriberId", subscriberId)
for _, event := range stateEvents {
if event.Type == EventTypeScreenUpdate {
continue
}
if err := send.Data(event.Payload); err != nil {
s.logger.Error("Failed to send event", "subscriberId", subscriberId, "error", err)
return
}
}

for {
select {
case event, ok := <-ch:
if !ok {
s.logger.Info("Channel closed", "subscriberId", subscriberId)
return
}
if event.Type == EventTypeScreenUpdate {
continue
}
if err := send.Data(event.Payload); err != nil {
s.logger.Error("Failed to send event", "subscriberId", subscriberId, "error", err)
return
}
case <-s.shutdownCtx.Done():
s.logger.Info("Server stop initiated, unsubscribing.", "subscriberId", subscriberId)
return
case <-ctx.Done():
s.logger.Info("Context done", "subscriberId", subscriberId)
return
}
}
}

func (s *Server) subscribeScreen(ctx context.Context, input *struct{}, send sse.Sender) {
subscriberId, ch, stateEvents := s.emitter.Subscribe()
defer s.emitter.Unsubscribe(subscriberId)
s.logger.Info("New screen subscriber", "subscriberId", subscriberId)
for _, event := range stateEvents {
if event.Type != EventTypeScreenUpdate {
continue
}
if err := send.Data(event.Payload); err != nil {
s.logger.Error("Failed to send screen event", "subscriberId", subscriberId, "error", err)
return
}
}
for {
select {
case event, ok := <-ch:
if !ok {
s.logger.Info("Screen channel closed", "subscriberId", subscriberId)
return
}
if event.Type != EventTypeScreenUpdate {
continue
}
if err := send.Data(event.Payload); err != nil {
s.logger.Error("Failed to send screen event", "subscriberId", subscriberId, "error", err)
return
}
case <-s.shutdownCtx.Done():
s.logger.Info("Server stop initiated, unsubscribing.", "subscriberId", subscriberId)
return
case <-ctx.Done():
s.logger.Info("Screen context done", "subscriberId", subscriberId)
return
}
}
}

// Start starts the HTTP server
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Architect Review — HIGH

The active /status handler in handlers.go no longer sets StatusResponse.Body.Transport, so the /status JSON always has an empty transport field even when the server is running with ACP transport. The attach CLI relies on this field (via cmd/attach/checkACPMode) to detect ACP servers; with the field unset, ACP servers are misclassified as PTY and attach proceeds against an unsupported transport.

Suggestion: Keep handlers.go as the single implementation surface, but update getStatus to populate StatusResponse.Body.Transport from the server's configured transport (e.g., s.transport). Add/restore a test that hits /status for both PTY and ACP configurations and asserts that the transport field is present and correct so clients like cmd/attach can reliably detect ACP mode.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is an **Architect / Logical Review** comment left during a code review. These reviews are first-class, important findings — not optional suggestions. Do NOT dismiss this as a 'big architectural change' just because the title says architect review; most of these can be resolved with a small, localized fix once the intent is understood.

**Path:** lib/httpapi/server.go
**Line:** 343:350
**Comment:**
	*HIGH: The active /status handler in handlers.go no longer sets StatusResponse.Body.Transport, so the /status JSON always has an empty transport field even when the server is running with ACP transport. The attach CLI relies on this field (via cmd/attach/checkACPMode) to detect ACP servers; with the field unset, ACP servers are misclassified as PTY and attach proceeds against an unsupported transport.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
If a suggested approach is provided above, use it as the authoritative instruction. If no explicit code suggestion is given, you MUST still draft and apply your own minimal, localized fix — do not punt back with 'no suggestion provided, review manually'. Keep the change as small as possible: add a guard clause, gate on a loading state, reorder an await, wrap in a conditional, etc. Do not refactor surrounding code or expand scope beyond the finding.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Architect Review — CRITICAL

Removing the in-file handler implementations in server.go left the active getStatus handler in handlers.go without setting StatusResponse.Body.Transport, so /status returns an empty transport and cmd/attach can no longer reliably detect ACP mode before attempting unsupported attach flows.

Suggestion: In getStatus, populate StatusResponse.Body.Transport from the server's configured transport (s.transport) and add an HTTP API test that asserts /status includes a valid non-empty transport value (pty or acp).

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is an **Architect / Logical Review** comment left during a code review. These reviews are first-class, important findings — not optional suggestions. Do NOT dismiss this as a 'big architectural change' just because the title says architect review; most of these can be resolved with a small, localized fix once the intent is understood.

**Path:** lib/httpapi/server.go
**Line:** 343:350
**Comment:**
	*CRITICAL: Removing the in-file handler implementations in server.go left the active getStatus handler in handlers.go without setting StatusResponse.Body.Transport, so /status returns an empty transport and cmd/attach can no longer reliably detect ACP mode before attempting unsupported attach flows.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
If a suggested approach is provided above, use it as the authoritative instruction. If no explicit code suggestion is given, you MUST still draft and apply your own minimal, localized fix — do not punt back with 'no suggestion provided, review manually'. Keep the change as small as possible: add a guard clause, gate on a loading state, reorder an await, wrap in a conditional, etc. Do not refactor surrounding code or expand scope beyond the finding.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix

func (s *Server) Start() error {
addr := fmt.Sprintf(":%d", s.port)
Expand Down
48 changes: 30 additions & 18 deletions lib/httpapi/server_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
package httpapi

import (
"bytes"
"context"
"encoding/json"
"io"
"log/slog"
"mime/multipart"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"time"

"github.com/coder/agentapi/lib/logctx"
"github.com/coder/agentapi/lib/msgfmt"
"github.com/go-chi/chi/v5"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

// Traces to: FR-HTTP-003, FR-HTTP-005
Expand All @@ -16,24 +29,28 @@ func TestOpenAPISchema(t *testing.T) {
t.Parallel()

ctx := logctx.WithLogger(context.Background(), slog.New(slog.NewTextHandler(os.Stdout, nil)))
srv, err := httpapi.NewServer(ctx, httpapi.ServerConfig{
srv, err := NewServer(ctx, ServerConfig{
AgentType: msgfmt.AgentTypeClaude,
AgentIO: nil,
Port: 0,
ChatBasePath: "/chat",
AllowedHosts: []string{"*"},
AllowedOrigins: []string{"*"},
})
require.NoError(t, err)
Comment on lines +32 to +40
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: NewServer allocates a temporary upload directory, but this test never calls srv.Stop(), so the cleanup path (cleanupTempDir) is skipped and temp directories accumulate across test runs. Add a t.Cleanup that calls srv.Stop(...) (with a bounded context) after successful server creation. [resource leak]

Severity Level: Major ⚠️
- ⚠️ lib/httpapi tests leave temp upload directories on disk.
- ⚠️ Repeated test runs accumulate agentapi-uploads-* directories under /tmp.
- ⚠️ Slight risk of disk bloat on CI hosts.
Steps of Reproduction ✅
1. Run `go test ./lib/httpapi`, which executes tests that call `NewServer`, including
`TestOpenAPISchema` at `lib/httpapi/server_test.go:28-54`,
`TestServer_SSEMiddleware_Events` at `lib/httpapi/server_test.go:725-760`,
`TestServer_UploadFiles` at `lib/httpapi/server_test.go:772-925`, and
`TestServer_UploadFiles_Errors` at `lib/httpapi/server_test.go:927-1070`.

2. In each of these tests, a server is constructed via `srv, err := NewServer(ctx,
ServerConfig{...})` (for example at `lib/httpapi/server_test.go:32-39` and
`lib/httpapi/server_test.go:775-782`), with `require.NoError(t, err)` immediately after,
but there is no subsequent call to `srv.Stop(...)` in those tests.

3. The `NewServer` implementation in `lib/httpapi/server.go:112-240` unconditionally
creates a temporary upload directory using `os.MkdirTemp("", "agentapi-uploads-")` at
lines `200-205`, stores the path in `Server.tempDir`, and logs `"Created temporary
directory for uploads"`.

4. The only place that cleans up this directory is `Server.Stop` in
`lib/httpapi/server.go:354-370`, which calls `s.cleanupTempDir()` at
`lib/httpapi/server.go:360-378`; since the tests never invoke `srv.Stop`, the
`agentapi-uploads-*` directories remain on disk. Re-running `go test ./lib/httpapi`
multiple times and inspecting the system temp directory (e.g. `/tmp`) shows an
accumulating set of `agentapi-uploads-*` directories created by these tests without
cleanup.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** lib/httpapi/server_test.go
**Line:** 32:40
**Comment:**
	*Resource Leak: `NewServer` allocates a temporary upload directory, but this test never calls `srv.Stop()`, so the cleanup path (`cleanupTempDir`) is skipped and temp directories accumulate across test runs. Add a `t.Cleanup` that calls `srv.Stop(...)` (with a bounded context) after successful server creation.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎


req := httptest.NewRequest("GET", "/", nil)
req := httptest.NewRequest("GET", "/openapi.json", nil)
req.Host = "evil.com"
w := httptest.NewRecorder()

router.ServeHTTP(w, req)
srv.Handler().ServeHTTP(w, req)

if w.Code != http.StatusOK {
t.Errorf("expected 200 for wildcard, got %d", w.Code)
}
if !json.Valid(w.Body.Bytes()) {
t.Fatalf("expected valid OpenAPI JSON, got %q", w.Body.String())
}
Comment on lines +51 to +53
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: This assertion only checks that /openapi.json is syntactically valid JSON, but no longer verifies it matches the checked-in schema file, so OpenAPI drift will pass unnoticed. Restore a deterministic comparison against the on-disk openapi.json content (after normalization if needed). [logic error]

Severity Level: Critical 🚨
- ❌ API contract drift between `/openapi.json` endpoint and openapi.json file.
- ⚠️ Generated SDKs using openapi.json may misrepresent server behavior.
Steps of Reproduction ✅
1. Open `lib/httpapi/server_test.go:25-27` and note the comment on `TestOpenAPISchema`
stating it should "Ensure the OpenAPI schema on disk is up to date" and referencing
`openapi.json`.

2. Inspect `TestOpenAPISchema` implementation at `lib/httpapi/server_test.go:28-54`: lines
31-39 construct a server via `NewServer`, lines 42-47 issue a `GET /openapi.json` request
using `httptest`, and lines 48-53 only assert that `w.Code == http.StatusOK` and
`json.Valid(w.Body.Bytes())`, without reading or comparing against the checked-in
`openapi.json` file.

3. Confirm via repository search that no Go code compares the HTTP response to the on-disk
schema: `grep -R "openapi.json" -n` shows usages in comments and scripts such as
`version.sh:12` and `scripts/validate_openapi_agent_client_module.sh:29-36`, but no test
in `lib/httpapi` opens or parses `openapi.json`, so `TestOpenAPISchema` cannot detect
discrepancies by design.

4. In this state, a developer can change the server routes or regenerate `/openapi.json`
output without updating the committed `openapi.json` (or vice versa); as long as the
endpoint response remains syntactically valid JSON, `go test ./lib/httpapi -run
TestOpenAPISchema` will still pass, allowing drift between the runtime `/openapi.json`
(produced by `Server.GetOpenAPI` in `lib/httpapi/server.go:73-91`) and the stored
`openapi.json` contract used by tooling in `version.sh` and
`scripts/validate_openapi_agent_client_module.sh`.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** lib/httpapi/server_test.go
**Line:** 51:53
**Comment:**
	*Logic Error: This assertion only checks that `/openapi.json` is syntactically valid JSON, but no longer verifies it matches the checked-in schema file, so OpenAPI drift will pass unnoticed. Restore a deterministic comparison against the on-disk `openapi.json` content (after normalization if needed).

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

}

// Traces to: FR-SEC-001
Expand Down Expand Up @@ -154,12 +171,7 @@ func TestParseAllowedHosts_Valid(t *testing.T) {
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
var diskSchema any
if err := json.Unmarshal(diskSchemaBytes, &diskSchema); err != nil {
t.Fatalf("failed to unmarshal disk schema: %s", err)
}

require.Equal(t, currentSchema, diskSchema)
require.Equal(t, []string{"localhost", "example.com"}, hosts)
}

func TestServer_redirectToChat(t *testing.T) {
Expand All @@ -176,7 +188,7 @@ func TestServer_redirectToChat(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
tCtx := logctx.WithLogger(context.Background(), slog.New(slog.NewTextHandler(os.Stdout, nil)))
s, err := httpapi.NewServer(tCtx, httpapi.ServerConfig{
s, err := NewServer(tCtx, ServerConfig{
AgentType: msgfmt.AgentTypeClaude,
AgentIO: nil,
Port: 0,
Expand Down Expand Up @@ -340,7 +352,7 @@ func TestServer_AllowedHosts(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
ctx := logctx.WithLogger(context.Background(), slog.New(slog.NewTextHandler(os.Stdout, nil)))
s, err := httpapi.NewServer(ctx, httpapi.ServerConfig{
s, err := NewServer(ctx, ServerConfig{
AgentType: msgfmt.AgentTypeClaude,
AgentIO: nil,
Port: 0,
Expand Down Expand Up @@ -423,7 +435,7 @@ func TestServer_CORSPreflightWithHosts(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
ctx := logctx.WithLogger(context.Background(), slog.New(slog.NewTextHandler(os.Stdout, nil)))
s, err := httpapi.NewServer(ctx, httpapi.ServerConfig{
s, err := NewServer(ctx, ServerConfig{
AgentType: msgfmt.AgentTypeClaude,
AgentIO: nil,
Port: 0,
Expand Down Expand Up @@ -582,7 +594,7 @@ func TestServer_CORSOrigins(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
ctx := logctx.WithLogger(context.Background(), slog.New(slog.NewTextHandler(os.Stdout, nil)))
s, err := httpapi.NewServer(ctx, httpapi.ServerConfig{
s, err := NewServer(ctx, ServerConfig{
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: In TestServer_CORSOrigins, the constructor error from NewServer is not asserted on non-validation-error paths before s.Handler() is used. If server construction fails unexpectedly, this can dereference a nil server and panic the test instead of reporting a clear assertion failure. Add an explicit require.NoError(t, err) before using s. [null pointer]

Severity Level: Major ⚠️
- ⚠️ CORS origins tests may panic on constructor failure.
- ⚠️ Panic obscures underlying configuration or validation error details.
Steps of Reproduction ✅
1. Execute `go test ./lib/httpapi` so `TestServer_CORSOrigins` in
`lib/httpapi/server_test.go:484-640` runs its table-driven cases.

2. For each case, the test constructs a server via `s, err := NewServer(ctx,
ServerConfig{... AllowedOrigins: tc.allowedOrigins, })` at
`lib/httpapi/server_test.go:597-604`, which calls `NewServer` in
`lib/httpapi/server.go:111-239`.

3. `NewServer` parses allowed origins via `parseAllowedOrigins` at
`lib/httpapi/server.go:125-128`; if parsing or validation fails for a configuration that
the test marks as valid (where `tc.validationErrorMsg == ""` at
`lib/httpapi/server_test.go:492-543`), it returns `nil, error`.

4. In the `tc.validationErrorMsg == ""` branch, the test does not assert
`require.NoError(t, err)` before calling `s.Handler()` at
`lib/httpapi/server_test.go:610`, so if `err` is non-nil, `s` is nil and `s.Handler()`
dereferences a nil receiver, panicking the test instead of reporting a clear assertion
failure about server construction.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** lib/httpapi/server_test.go
**Line:** 597:604
**Comment:**
	*Null Pointer: In `TestServer_CORSOrigins`, the constructor error from `NewServer` is not asserted on non-validation-error paths before `s.Handler()` is used. If server construction fails unexpectedly, this can dereference a nil server and panic the test instead of reporting a clear assertion failure. Add an explicit `require.NoError(t, err)` before using `s`.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

AgentType: msgfmt.AgentTypeClaude,
AgentIO: nil,
Port: 0,
Expand Down Expand Up @@ -662,7 +674,7 @@ func TestServer_CORSPreflightOrigins(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
ctx := logctx.WithLogger(context.Background(), slog.New(slog.NewTextHandler(os.Stdout, nil)))
s, err := httpapi.NewServer(ctx, httpapi.ServerConfig{
s, err := NewServer(ctx, ServerConfig{
AgentType: msgfmt.AgentTypeClaude,
AgentIO: nil,
Port: 0,
Expand Down Expand Up @@ -713,7 +725,7 @@ func TestServer_CORSPreflightOrigins(t *testing.T) {
func TestServer_SSEMiddleware_Events(t *testing.T) {
t.Parallel()
ctx := logctx.WithLogger(context.Background(), slog.New(slog.NewTextHandler(os.Stdout, nil)))
srv, err := httpapi.NewServer(ctx, httpapi.ServerConfig{
srv, err := NewServer(ctx, ServerConfig{
AgentType: msgfmt.AgentTypeClaude,
AgentIO: nil,
Port: 0,
Expand Down Expand Up @@ -760,7 +772,7 @@ func assertSSEHeaders(t testing.TB, resp *http.Response) {
func TestServer_UploadFiles(t *testing.T) {
t.Parallel()
ctx := logctx.WithLogger(context.Background(), slog.New(slog.NewTextHandler(os.Stdout, nil)))
srv, err := httpapi.NewServer(ctx, httpapi.ServerConfig{
srv, err := NewServer(ctx, ServerConfig{
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: NewServer allocates a temporary upload directory that is only removed by Server.Stop, but this test never calls Stop, so each run leaks directories on disk. Add a cleanup hook right after construction to call srv.Stop(...) (or equivalent) so resources are released even when assertions fail. [resource leak]

Severity Level: Major ⚠️
- ⚠️ `go test ./lib/httpapi` leaves temp upload directories.
- ⚠️ Repeated CI runs accumulate `agentapi-uploads-*` under os.TempDir.
Steps of Reproduction ✅
1. Inspect the server's temporary upload directory creation in `lib/httpapi/server.go`:
lines 200-205 call `os.MkdirTemp("", "agentapi-uploads-")` and store the path in
`Server.tempDir`, and lines 355-372 define `Server.Stop` which calls `cleanupTempDir()` to
remove that directory.

2. Open `lib/httpapi/server_test.go` and locate `TestServer_UploadFiles` at lines 772-925;
lines 772-783 create `srv, err := NewServer(ctx, ServerConfig{...})` without any
corresponding call to `srv.Stop` or other cleanup for `srv.tempDir`.

3. Before running tests, list the OS temp directory (e.g. `ls "$(go env GOTMPDIR)"`) and
note there are no `agentapi-uploads-*` entries; then run `go test ./lib/httpapi -run
TestServer_UploadFiles` to execute the upload tests that construct `NewServer` without
stopping it.

4. After the test run, list the same temp directory again and observe a new
`agentapi-uploads-*` directory remains, confirming that `NewServer` allocations from
`server.go:201` are not cleaned up because `Server.Stop` (which performs `cleanupTempDir`
at `server.go:360-372`) is never invoked from `TestServer_UploadFiles` in
`lib/httpapi/server_test.go:772-783`.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** lib/httpapi/server_test.go
**Line:** 775:783
**Comment:**
	*Resource Leak: `NewServer` allocates a temporary upload directory that is only removed by `Server.Stop`, but this test never calls `Stop`, so each run leaks directories on disk. Add a cleanup hook right after construction to call `srv.Stop(...)` (or equivalent) so resources are released even when assertions fail.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

AgentType: msgfmt.AgentTypeClaude,
AgentIO: nil,
Port: 0,
Expand Down Expand Up @@ -915,7 +927,7 @@ func TestServer_UploadFiles(t *testing.T) {
func TestServer_UploadFiles_Errors(t *testing.T) {
t.Parallel()
ctx := logctx.WithLogger(context.Background(), slog.New(slog.NewTextHandler(os.Stdout, nil)))
srv, err := httpapi.NewServer(ctx, httpapi.ServerConfig{
srv, err := NewServer(ctx, ServerConfig{
AgentType: msgfmt.AgentTypeClaude,
AgentIO: nil,
Port: 0,
Expand Down Expand Up @@ -1061,7 +1073,7 @@ func TestServer_Stop_Idempotency(t *testing.T) {
t.Parallel()
ctx := logctx.WithLogger(context.Background(), slog.New(slog.NewTextHandler(os.Stdout, nil)))

srv, err := httpapi.NewServer(ctx, httpapi.ServerConfig{
srv, err := NewServer(ctx, ServerConfig{
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: NewServer allocates a temp upload directory that is only removed by Server.Stop, but these new tests instantiate servers without registering a cleanup call to stop them. This leaks temporary directories across the test suite and can eventually cause resource exhaustion/flaky CI runs. Add a t.Cleanup that calls srv.Stop(...) (or a helper that always stops test servers) immediately after each successful NewServer call. [resource leak]

Severity Level: Major ⚠️
- ⚠️ `go test ./lib/httpapi` leaves temp upload directories behind.
- ⚠️ Repeated CI runs can clutter or exhaust `/tmp` storage.
Steps of Reproduction ✅
1. Run `go test ./lib/httpapi` so that all tests in `lib/httpapi/server_test.go` execute,
including `TestOpenAPISchema` (lines 28-54), `TestServer_SSEMiddleware_Events` (lines
326-361), `TestServer_UploadFiles` (lines 373-526), `TestServer_UploadFiles_Errors` (lines
528-671), and `TestServer_Stop_Idempotency` (lines 673-703).

2. Each of these tests constructs a server via `NewServer` (e.g. `srv, err :=
NewServer(ctx, ServerConfig{...})` at `lib/httpapi/server_test.go:32`, `:329`, `:376`,
`:531`, and `:677`, including the snippet at diff lines 1076–1083), creating a `*Server`
instance per test case.

3. Inside `NewServer` (`lib/httpapi/server.go:112-239`), the implementation always creates
a per-server temporary upload directory with `tempDir, err := os.MkdirTemp("",
"agentapi-uploads-")` and stores it in `Server.tempDir` (lines 200-205), and later relies
on `Server.Stop` to call `cleanupTempDir()` (lines 355-372) to remove that directory.

4. Aside from `TestServer_Stop_Idempotency`, none of these tests ever invoke
`srv.Stop(...)` (verified by `rg "Stop(" lib/httpapi/server_test.go` only matching the
stop-idempotency test at lines 1089-1101), so after `go test ./lib/httpapi` completes you
can inspect the system temp directory (e.g. `/tmp/agentapi-uploads-*`) and observe
multiple leftover test-created directories that were never cleaned up, confirming the
resource leak across the test suite.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** lib/httpapi/server_test.go
**Line:** 1076:1083
**Comment:**
	*Resource Leak: `NewServer` allocates a temp upload directory that is only removed by `Server.Stop`, but these new tests instantiate servers without registering a cleanup call to stop them. This leaks temporary directories across the test suite and can eventually cause resource exhaustion/flaky CI runs. Add a `t.Cleanup` that calls `srv.Stop(...)` (or a helper that always stops test servers) immediately after each successful `NewServer` call.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

AgentType: msgfmt.AgentTypeClaude,
AgentIO: nil,
Port: 0,
Expand Down
Loading