{"record":{"id":"1e3c5058bf2117b8","repo":"charmbracelet/crush","slug":"failed-to-send-message-to-agent-status-code-d-1e3c50","errorCode":null,"errorMessage":"failed to send message to agent: status code %d","messagePattern":"failed to send message to agent: status code (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/client/proto.go","lineNumber":508,"sourceCode":"// for completion detection. Pass \"\" when the caller does not need\n// to distinguish its own turn's terminal event from any concurrent\n// turn on the same session (e.g. interactive TUI usage).\nfunc (c *Client) SendMessage(ctx context.Context, id string, sessionID, runID, prompt string, attachments ...message.Attachment) error {\n\trsp, err := c.post(ctx, fmt.Sprintf(\"/workspaces/%s/agent\", id), nil, jsonBody(proto.AgentMessage{\n\t\tSessionID:   sessionID,\n\t\tRunID:       runID,\n\t\tPrompt:      prompt,\n\t\tAttachments: proto.AttachmentsFromMessage(attachments),\n\t}), http.Header{\"Content-Type\": []string{\"application/json\"}})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to send message to agent: %w\", err)\n\t}\n\tdefer rsp.Body.Close()\n\tif rsp.StatusCode != http.StatusOK && rsp.StatusCode != http.StatusAccepted {\n\t\tif msg := decodeErrorMessage(rsp.Body); msg != \"\" {\n\t\t\treturn fmt.Errorf(\"failed to send message to agent: status code %d: %s\", rsp.StatusCode, msg)\n\t\t}\n\t\treturn fmt.Errorf(\"failed to send message to agent: status code %d\", rsp.StatusCode)\n\t}\n\treturn nil\n}\n\n// decodeErrorMessage attempts to decode the response body as a\n// proto.Error and returns its message. It returns an empty string\n// when the body is empty or cannot be decoded into a proto.Error\n// with a non-empty message, letting callers fall back to a\n// status-only error.\nfunc decodeErrorMessage(body io.Reader) string {\n\tvar e proto.Error\n\tif err := json.NewDecoder(body).Decode(&e); err != nil {\n\t\treturn \"\"\n\t}\n\treturn e.Message\n}\n\n// RunShellCommand runs a shell command in the workspace without triggering the agent.","sourceCodeStart":490,"sourceCodeEnd":526,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/client/proto.go#L490-L526","documentation":"Fallback variant of the SendMessage status error: the server returned a non-200/202 status and decodeErrorMessage returned an empty string (empty, malformed, or non-JSON body), so only the numeric status code is reported. It indicates the server failed without a usable error body.","triggerScenarios":"Non-200/202 response whose body is empty, HTML (e.g. a reverse-proxy 502 page), truncated, or not decodable as proto.Error.","commonSituations":"Load balancer or proxy returning 502/503/504 with HTML pages; server crash before writing a JSON error; request rejected at the auth layer with an empty body.","solutions":["Treat the raw status as the only signal: 502/503/504 imply proxy or server availability problems — check upstream health.","Capture the response body separately (via a debugging proxy or server logs) since the client discards it here.","Retry with backoff for 5xx; fix auth for 401/403.","Check whether an ingress/proxy sits between client and server and is masking the real response."],"exampleFix":"// before: assuming err contains server detail\nlog.Fatalf(\"send: %v\", err) // only 'status code 502'\n// after: probe server health on opaque gateway errors\nif strings.Contains(err.Error(), \"status code 50\") {\n    if healthErr := probeHealth(ctx); healthErr != nil {\n        return fmt.Errorf(\"server unhealthy: %w (send failed: %v)\", healthErr, err)\n    }\n}","handlingStrategy":"fallback","validationCode":"// Go: verify direct server reachability, bypassing any proxy assumption\nresp, err := http.Get(serverBaseURL + \"/health\")\nif err != nil || resp.StatusCode != 200 {\n    return fmt.Errorf(\"server unreachable via configured base URL\")\n}","typeGuard":"// Go: detect opaque gateway statuses\nfunc isGatewayStatus(err error) bool {\n    s := err.Error()\n    return strings.Contains(s, \"status code 502\") ||\n        strings.Contains(s, \"status code 503\") ||\n        strings.Contains(s, \"status code 504\")\n}","tryCatchPattern":"if err := client.SendMessage(ctx, wsID, sid, runID, prompt, nil); err != nil {\n    if isGatewayStatus(err) {\n        return fmt.Errorf(\"gateway/proxy in front of the server failed: %w\", err)\n    }\n    return err\n}","preventionTips":["Audit proxies/load balancers between client and server; HTML 502 pages cause this opaque error.","Ask the server team to always emit a JSON error body so decodeErrorMessage can extract detail.","For 5xx, retry with exponential backoff.","Correlate with server-side request logs since the client discards the body here."],"tags":["http-status","message-send","proxy","opaque-error"],"backgroundTag":"unexpected-http-status","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}