{"record":{"id":"14f5349a375f255f","repo":"charmbracelet/crush","slug":"failed-to-get-session-agent-info-w","errorCode":null,"errorMessage":"failed to get session agent info: %w","messagePattern":"failed to get session agent info: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/client/proto.go","lineNumber":550,"sourceCode":"\tif err != nil {\n\t\treturn proto.ShellCommandResponse{}, fmt.Errorf(\"failed to run shell command: %w\", err)\n\t}\n\tdefer rsp.Body.Close()\n\tif rsp.StatusCode != http.StatusOK {\n\t\treturn proto.ShellCommandResponse{}, fmt.Errorf(\"failed to run shell command: status code %d\", rsp.StatusCode)\n\t}\n\tvar resp proto.ShellCommandResponse\n\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}","sourceCodeStart":532,"sourceCodeEnd":568,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/client/proto.go#L532-L568","documentation":"GetAgentSessionInfo GETs /workspaces/{id}/agent/sessions/{sid} and returns *proto.AgentSession. This error wraps a transport failure from c.get (server unreachable, connection refused/reset, timeout, malformed URL from bad ids) before the response is examined, with the cause preserved via %w.","triggerScenarios":"Calling GetAgentSessionInfo (e.g. from AgentIsSessionBusy) when the server is down, DNS fails, the connection times out, or the URL is malformed due to an invalid workspace/session id.","commonSituations":"Busy-check before sending a message while the server is restarting; whitespace or empty ids producing a bad URL; CI runners without network access to the server.","solutions":["Confirm the server is running and reachable at the configured base URL.","Validate id strings are non-empty and properly path-safe before building the request.","Inspect the wrapped cause (%w) for the concrete transport failure.","Add retry with backoff for transient connectivity blips in polling loops like AgentIsSessionBusy."],"exampleFix":"// before: single-shot call inside a polling loop\nbusy, err := AgentIsSessionBusy(ctx, client, wsID, sid)\n// after\ninfo, err := client.GetAgentSessionInfo(ctx, wsID, sid)\nif err != nil {\n    select {\n    case <-time.After(500 * time.Millisecond):\n        return AgentIsSessionBusy(ctx, client, wsID, sid) // bounded retry\n    case <-ctx.Done():\n        return ctx.Err()\n    }\n}","handlingStrategy":"retry","validationCode":"// Go: validate IDs before building the request URL\nif workspaceID == \"\" || sessionID == \"\" {\n    return fmt.Errorf(\"workspace and session ids required\")\n}\nif strings.ContainsAny(workspaceID+sessionID, \" /?#\") {\n    return fmt.Errorf(\"ids must be URL-path safe\")\n}","typeGuard":"// Go: separate transport errors from HTTP-status errors\nfunc isTransportError(err error) bool {\n    return err != nil && !strings.Contains(err.Error(), \"status code\")\n}","tryCatchPattern":"info, err := client.GetAgentSessionInfo(ctx, wsID, sid)\nfor i := 0; err != nil && isTransportError(err) && i < 3; i++ {\n    select {\n    case <-time.After(time.Duration(250*(1<<i)) * time.Millisecond):\n        info, err = client.GetAgentSessionInfo(ctx, wsID, sid)\n    case <-ctx.Done():\n        return nil, ctx.Err()\n    }\n}\nif err != nil { return nil, err }","preventionTips":["Bound retries in polling loops with context cancellation.","Smoke-test connectivity before polling bursts.","Normalize/trim IDs at construction time to avoid malformed URLs.","Distinguish transport from status errors before choosing to retry."],"tags":["network","http-client","session-info"],"backgroundTag":"http-request-failed","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}