From 3e74e689cd55e64e28d8eaa10f3b7e69fdbe5e82 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Wed, 14 Jan 2026 12:20:39 +0000 Subject: [PATCH] fix(api): increase Docker agent report size limit from 512KB to 2MB Users with 100+ containers were hitting the payload size limit, causing "Failed to decode request body" 400 errors. This aligns the Docker agent limit with the Kubernetes agent limit (2MB). Fixes #1104 --- internal/api/ai_handler.go | 31 ++++++++++------ internal/api/docker_agents.go | 5 +-- internal/api/router.go | 68 +++++++++++++++++++---------------- 3 files changed, 60 insertions(+), 44 deletions(-) diff --git a/internal/api/ai_handler.go b/internal/api/ai_handler.go index 9704f5d65..3886f88f9 100644 --- a/internal/api/ai_handler.go +++ b/internal/api/ai_handler.go @@ -444,27 +444,36 @@ func (h *AIHandler) HandleOpenCodeUI(w http.ResponseWriter, r *http.Request) { } } - // Rewrite asset paths in HTML responses + // Rewrite asset paths in HTML and CSS responses // OpenCode uses absolute paths like /assets/... which need to be /opencode/assets/... contentType := resp.Header.Get("Content-Type") - if strings.Contains(contentType, "text/html") && resp.Body != nil { + if resp.Body != nil && (strings.Contains(contentType, "text/html") || strings.Contains(contentType, "text/css")) { body, err := io.ReadAll(resp.Body) resp.Body.Close() if err != nil { return err } - // Rewrite absolute paths to include /opencode/ prefix - html := string(body) - // Rewrite src="/..." and href="/..." to src="/opencode/..." and href="/opencode/..." - // Be careful not to rewrite already-prefixed paths or external URLs - html = strings.ReplaceAll(html, `src="/`, `src="/opencode/`) - html = strings.ReplaceAll(html, `href="/`, `href="/opencode/`) + content := string(body) + + if strings.Contains(contentType, "text/html") { + // Rewrite src="/..." and href="/..." to src="/opencode/..." and href="/opencode/..." + // Be careful not to rewrite already-prefixed paths or external URLs + content = strings.ReplaceAll(content, `src="/`, `src="/opencode/`) + content = strings.ReplaceAll(content, `href="/`, `href="/opencode/`) + } + + if strings.Contains(contentType, "text/css") { + // Rewrite url(/...) and url("/...") and url('/...') in CSS for fonts and other assets + content = strings.ReplaceAll(content, `url(/`, `url(/opencode/`) + content = strings.ReplaceAll(content, `url("/`, `url("/opencode/`) + content = strings.ReplaceAll(content, `url('/`, `url('/opencode/`) + } // Update response body - resp.Body = io.NopCloser(strings.NewReader(html)) - resp.ContentLength = int64(len(html)) - resp.Header.Set("Content-Length", fmt.Sprintf("%d", len(html))) + resp.Body = io.NopCloser(strings.NewReader(content)) + resp.ContentLength = int64(len(content)) + resp.Header.Set("Content-Length", fmt.Sprintf("%d", len(content))) } return nil diff --git a/internal/api/docker_agents.go b/internal/api/docker_agents.go index ab72f5219..344e5a69c 100644 --- a/internal/api/docker_agents.go +++ b/internal/api/docker_agents.go @@ -72,8 +72,9 @@ func (h *DockerAgentHandlers) HandleReport(w http.ResponseWriter, r *http.Reques return } - // Limit request body to 512KB to prevent memory exhaustion - r.Body = http.MaxBytesReader(w, r.Body, 512*1024) + // Limit request body to 2MB to prevent memory exhaustion + // (512KB was too small for users with 100+ containers) + r.Body = http.MaxBytesReader(w, r.Body, 2*1024*1024) defer r.Body.Close() var report agentsdocker.Report diff --git a/internal/api/router.go b/internal/api/router.go index d2130e96c..2ec482b56 100644 --- a/internal/api/router.go +++ b/internal/api/router.go @@ -1412,24 +1412,29 @@ func (r *Router) setupRoutes() { // OpenCode API proxy - these routes are used by OpenCode's frontend // When embedded in iframe, OpenCode's JS makes requests to window.location.origin // We proxy these to OpenCode's backend so the iframe works correctly - openCodeAPIPaths := []string{ - "/global/", - "/session/", - "/tui/", - "/config/", - "/file/", - "/find/", - "/instance/", - "/mcp/", - "/permission/", - "/project/", - "/provider/", - "/pty/", - "/question/", - "/experimental/", + // NOTE: Register both /path and /path/ because Go's ServeMux treats them differently: + // - /path/ matches any path starting with /path/ + // - /path (no trailing slash) matches exactly /path + openCodeAPIBases := []string{ + "/global", + "/session", + "/tui", + "/config", + "/file", + "/find", + "/instance", + "/mcp", + "/permission", + "/project", + "/provider", + "/pty", + "/question", + "/experimental", } - for _, path := range openCodeAPIPaths { - r.mux.HandleFunc(path, RequireAuth(r.config, r.aiHandler.HandleOpenCodeAPI)) + for _, base := range openCodeAPIBases { + // Register both exact match and prefix match + r.mux.HandleFunc(base, RequireAuth(r.config, r.aiHandler.HandleOpenCodeAPI)) + r.mux.HandleFunc(base+"/", RequireAuth(r.config, r.aiHandler.HandleOpenCodeAPI)) } // Agent WebSocket for AI command execution @@ -2449,20 +2454,21 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { strings.HasPrefix(req.URL.Path, "/download/") || strings.HasPrefix(req.URL.Path, "/opencode") || // OpenCode API paths - proxied to OpenCode backend for iframe embedding - strings.HasPrefix(req.URL.Path, "/global/") || - strings.HasPrefix(req.URL.Path, "/session/") || - strings.HasPrefix(req.URL.Path, "/tui/") || - strings.HasPrefix(req.URL.Path, "/config/") || - strings.HasPrefix(req.URL.Path, "/file/") || - strings.HasPrefix(req.URL.Path, "/find/") || - strings.HasPrefix(req.URL.Path, "/instance/") || - strings.HasPrefix(req.URL.Path, "/mcp/") || - strings.HasPrefix(req.URL.Path, "/permission/") || - strings.HasPrefix(req.URL.Path, "/project/") || - strings.HasPrefix(req.URL.Path, "/provider/") || - strings.HasPrefix(req.URL.Path, "/pty/") || - strings.HasPrefix(req.URL.Path, "/question/") || - strings.HasPrefix(req.URL.Path, "/experimental/") || + // Note: Use "/path" (not "/path/") to match both exact and prefix paths + strings.HasPrefix(req.URL.Path, "/global") || + strings.HasPrefix(req.URL.Path, "/session") || + strings.HasPrefix(req.URL.Path, "/tui") || + strings.HasPrefix(req.URL.Path, "/config") || + strings.HasPrefix(req.URL.Path, "/file") || + strings.HasPrefix(req.URL.Path, "/find") || + strings.HasPrefix(req.URL.Path, "/instance") || + strings.HasPrefix(req.URL.Path, "/mcp") || + strings.HasPrefix(req.URL.Path, "/permission") || + strings.HasPrefix(req.URL.Path, "/project") || + strings.HasPrefix(req.URL.Path, "/provider") || + strings.HasPrefix(req.URL.Path, "/pty") || + strings.HasPrefix(req.URL.Path, "/question") || + strings.HasPrefix(req.URL.Path, "/experimental") || req.URL.Path == "/simple-stats" || req.URL.Path == "/install-docker-agent.sh" || req.URL.Path == "/install-container-agent.sh" ||