{"record":{"id":"2b36b8e7a1d15866","repo":"charmbracelet/crush","slug":"failed-to-decode-session-agent-info-w","errorCode":null,"errorMessage":"failed to decode session agent info: %w","messagePattern":"failed to decode session agent info: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/client/proto.go","lineNumber":558,"sourceCode":"\tif err := json.NewDecoder(rsp.Body).Decode(&resp); err != nil {\n\t\treturn proto.ShellCommandResponse{}, fmt.Errorf(\"failed to decode shell command response: %w\", err)\n\t}\n\treturn resp, nil\n}\n\n// GetAgentSessionInfo retrieves the agent session info for a workspace.\nfunc (c *Client) GetAgentSessionInfo(ctx context.Context, id string, sessionID string) (*proto.AgentSession, error) {\n\trsp, err := c.get(ctx, fmt.Sprintf(\"/workspaces/%s/agent/sessions/%s\", id, sessionID), nil, nil)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get session agent info: %w\", err)\n\t}\n\tdefer rsp.Body.Close()\n\tif rsp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"failed to get session agent info: status code %d\", rsp.StatusCode)\n\t}\n\tvar info proto.AgentSession\n\tif err := json.NewDecoder(rsp.Body).Decode(&info); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to decode session agent info: %w\", err)\n\t}\n\treturn &info, nil\n}\n\n// AgentSummarizeSession requests a session summarization.\nfunc (c *Client) AgentSummarizeSession(ctx context.Context, id string, sessionID string) error {\n\trsp, err := c.post(ctx, fmt.Sprintf(\"/workspaces/%s/agent/sessions/%s/summarize\", id, sessionID), nil, nil, nil)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to summarize session: %w\", err)\n\t}\n\tdefer rsp.Body.Close()\n\tif rsp.StatusCode != http.StatusOK {\n\t\treturn fmt.Errorf(\"failed to summarize session: status code %d\", rsp.StatusCode)\n\t}\n\treturn nil\n}\n\n// InitiateAgentProcessing triggers agent initialization on the server.","sourceCodeStart":540,"sourceCodeEnd":576,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/client/proto.go#L540-L576","documentation":"Thrown by Client.GetAgentSessionInfo in internal/client/proto.go when the JSON response body from GET /workspaces/{id}/agent/sessions/{id} cannot be decoded into proto.AgentSession. It wraps the underlying json.Decoder error, so the cause is a malformed, truncated, or non-JSON body (e.g. an HTML error page or a proxy gateway response) that slipped past the 200-status check. Callers such as AgentIsSessionBusy then cannot determine the session's busy state.","triggerScenarios":"Calling GetAgentSessionInfo when the server returns HTTP 200 but with a body that is not valid JSON matching proto.AgentSession: empty body, HTML from a misconfigured reverse proxy, JSON with incompatible field types (e.g. string where a number is expected), or a truncated response from a dropped connection mid-body.","commonSituations":"Running behind an nginx/traefik proxy that intercepts the request; pointing the client at a wrong port where another service answers 200; server version mismatch where the API returns a changed schema; TLS-terminating proxy injecting an error page; connection cut before the body finished streaming.","solutions":["Read the wrapped cause (errors.Unwrap / %w chain) to see the exact json.SyntaxError or UnmarshalTypeError and fix the payload accordingly.","Verify the client is pointed at the correct crush server address/port and that no proxy is intercepting the request (curl the endpoint directly).","Confirm client and server versions match so the AgentSession JSON schema is the same on both sides.","Retry the call — a truncated body from a transient network drop will succeed on retry."],"exampleFix":"// before: cannot tell decode failure from empty body\ninfo, err := client.GetAgentSessionInfo(ctx, wsID, sessionID)\nif err != nil {\n    return err\n}\n// after: inspect the wrapped cause\ninfo, err := client.GetAgentSessionInfo(ctx, wsID, sessionID)\nif err != nil {\n    var syn *json.SyntaxError\n    if errors.As(err, &syn) {\n        log.Printf(\"non-JSON response at offset %d; check proxy/server address\", syn.Offset)\n    }\n    return fmt.Errorf(\"session agent info unavailable: %w\", err)\n}","handlingStrategy":"try-catch","validationCode":"// Pre-flight: confirm the endpoint returns JSON before calling the client\nrsp, err := http.Get(serverURL + \"/workspaces/\" + wsID + \"/agent/sessions/\" + sessionID)\nif err == nil {\n    defer rsp.Body.Close()\n    ct := rsp.Header.Get(\"Content-Type\")\n    if !strings.Contains(ct, \"application/json\") {\n        return fmt.Errorf(\"expected JSON, got %q — check proxy/server address\", ct)\n    }\n}","typeGuard":"func isDecodeError(err error) bool {\n    var syn *json.SyntaxError\n    var ute *json.UnmarshalTypeError\n    return errors.As(err, &syn) || errors.As(err, &ute)\n}","tryCatchPattern":"info, err := client.GetAgentSessionInfo(ctx, wsID, sessionID)\nif err != nil {\n    var syn *json.SyntaxError\n    if errors.As(err, &syn) {\n        // non-JSON body: likely proxy or wrong endpoint\n        return retryWithDirectConnection(ctx, wsID, sessionID)\n    }\n    return err\n}","preventionTips":["Always unwrap and classify the decode error with errors.As before treating it as fatal","Point the client directly at the server in dev to rule out proxy interference","Pin client and server to matching versions so the AgentSession schema stays aligned","Prefer retry for truncated bodies caused by transient network drops"],"tags":["network","json","http-client","decoding"],"backgroundTag":"json-decode-failed","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}