fix(server): remove 60s A2A client timeout + correct file-read cat args

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 <rootPath> <filePath>` 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) <noreply@anthropic.com>
This commit is contained in:
Hongming Wang 2026-04-23 17:25:53 -07:00
parent e337efe974
commit 18ebb1d7bf
2 changed files with 13 additions and 7 deletions

View File

@ -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

View File

@ -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,