From 18ebb1d7bf7a438ed20f09acbf086fa63d09720e Mon Sep 17 00:00:00 2001 From: Hongming Wang Date: Thu, 23 Apr 2026 17:25:53 -0700 Subject: [PATCH] fix(server): remove 60s A2A client timeout + correct file-read cat args MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two bugs surfaced while testing Claude Code + OAuth deploys: 1. A2A proxy: a2aClient had a 60s Client.Timeout "safety net" that defeated the per-request context deadlines the code otherwise sets (canvas = 5m, agent-to-agent = 30m). Claude Code's first-token cold start over OAuth takes 30-60s, so every first "hi" into a fresh claude-code workspace returned 503 at exactly the 1m mark. Removed the Client.Timeout — the context deadline now governs as documented in the adjacent comment. 2. Files tab: ReadFile ran `cat ` as two args to cat. `cat /home agent/turtle_draw.py` tries to read the rootPath directory (errors "Is a directory") and then resolves the filePath relative to the container cwd, which is not guaranteed to equal rootPath. Result: the file-content pane stayed blank even though the file listed fine. Join into a single path before exec. Co-Authored-By: Claude Opus 4.7 (1M context) --- workspace-server/internal/handlers/a2a_proxy.go | 12 +++++++----- workspace-server/internal/handlers/templates.go | 8 ++++++-- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/workspace-server/internal/handlers/a2a_proxy.go b/workspace-server/internal/handlers/a2a_proxy.go index d1707070..044d7c49 100644 --- a/workspace-server/internal/handlers/a2a_proxy.go +++ b/workspace-server/internal/handlers/a2a_proxy.go @@ -89,11 +89,13 @@ func isSystemCaller(callerID string) bool { const maxProxyResponseBody = 10 << 20 // a2aClient is a shared HTTP client for proxying A2A requests to workspace agents. -// No client-level timeout — timeouts are enforced per-request via context deadlines: -// canvas = 5 min (Rule 3), agent-to-agent = 30 min (DoS cap). -var a2aClient = &http.Client{ - Timeout: 60 * time.Second, // Safety net for when context deadlines are missing -} +// No client-level timeout — timeouts are enforced per-request via context +// deadlines: canvas = 5 min (Rule 3), agent-to-agent = 30 min (DoS cap). Do NOT +// set a Client.Timeout here: it is enforced independently of ctx deadlines and +// would pre-empt legitimate slow cold-start flows (e.g. Claude Code first-token +// over OAuth can take 30-60s on boot). Callers that want a safety net should +// build a context.WithTimeout themselves. +var a2aClient = &http.Client{} type proxyA2AError struct { Status int diff --git a/workspace-server/internal/handlers/templates.go b/workspace-server/internal/handlers/templates.go index 833fb73c..737f5b06 100644 --- a/workspace-server/internal/handlers/templates.go +++ b/workspace-server/internal/handlers/templates.go @@ -298,9 +298,13 @@ func (h *TemplatesHandler) ReadFile(c *gin.Context) { return } - // Try container first + // Try container first. `cat` wants a single path argument — passing + // rootPath and filePath as two args would make `cat` try to read the + // rootPath directory (error) and then resolve filePath relative to + // the container's cwd, which isn't guaranteed to equal rootPath. if containerName := h.findContainer(ctx, workspaceID); containerName != "" { - content, err := h.execInContainer(ctx, containerName, []string{"cat", rootPath, filePath}) + fullPath := strings.TrimRight(rootPath, "/") + "/" + filePath + content, err := h.execInContainer(ctx, containerName, []string{"cat", fullPath}) if err == nil { c.JSON(http.StatusOK, gin.H{ "path": filePath,