charmbracelet/crush · error
failed to decode session agent info: %w
Error message
failed to decode session agent info: %w
What it means
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.
Source
Thrown at internal/client/proto.go:558
if err := json.NewDecoder(rsp.Body).Decode(&resp); err != nil {
return proto.ShellCommandResponse{}, fmt.Errorf("failed to decode shell command response: %w", err)
}
return resp, nil
}
// GetAgentSessionInfo retrieves the agent session info for a workspace.
func (c *Client) GetAgentSessionInfo(ctx context.Context, id string, sessionID string) (*proto.AgentSession, error) {
rsp, err := c.get(ctx, fmt.Sprintf("/workspaces/%s/agent/sessions/%s", id, sessionID), nil, nil)
if err != nil {
return nil, fmt.Errorf("failed to get session agent info: %w", err)
}
defer rsp.Body.Close()
if rsp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to get session agent info: status code %d", rsp.StatusCode)
}
var info proto.AgentSession
if err := json.NewDecoder(rsp.Body).Decode(&info); err != nil {
return nil, fmt.Errorf("failed to decode session agent info: %w", err)
}
return &info, nil
}
// AgentSummarizeSession requests a session summarization.
func (c *Client) AgentSummarizeSession(ctx context.Context, id string, sessionID string) error {
rsp, err := c.post(ctx, fmt.Sprintf("/workspaces/%s/agent/sessions/%s/summarize", id, sessionID), nil, nil, nil)
if err != nil {
return fmt.Errorf("failed to summarize session: %w", err)
}
defer rsp.Body.Close()
if rsp.StatusCode != http.StatusOK {
return fmt.Errorf("failed to summarize session: status code %d", rsp.StatusCode)
}
return nil
}
// InitiateAgentProcessing triggers agent initialization on the server.View on GitHub (pinned to 7944b8e522)
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.
Example fix
// before: cannot tell decode failure from empty body
info, err := client.GetAgentSessionInfo(ctx, wsID, sessionID)
if err != nil {
return err
}
// after: inspect the wrapped cause
info, err := client.GetAgentSessionInfo(ctx, wsID, sessionID)
if err != nil {
var syn *json.SyntaxError
if errors.As(err, &syn) {
log.Printf("non-JSON response at offset %d; check proxy/server address", syn.Offset)
}
return fmt.Errorf("session agent info unavailable: %w", err)
} Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-flight: confirm the endpoint returns JSON before calling the client
rsp, err := http.Get(serverURL + "/workspaces/" + wsID + "/agent/sessions/" + sessionID)
if err == nil {
defer rsp.Body.Close()
ct := rsp.Header.Get("Content-Type")
if !strings.Contains(ct, "application/json") {
return fmt.Errorf("expected JSON, got %q — check proxy/server address", ct)
}
} Type guard
func isDecodeError(err error) bool {
var syn *json.SyntaxError
var ute *json.UnmarshalTypeError
return errors.As(err, &syn) || errors.As(err, &ute)
} Try / catch
info, err := client.GetAgentSessionInfo(ctx, wsID, sessionID)
if err != nil {
var syn *json.SyntaxError
if errors.As(err, &syn) {
// non-JSON body: likely proxy or wrong endpoint
return retryWithDirectConnection(ctx, wsID, sessionID)
}
return err
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- failed to decode grant permission response: %w
- failed to decode answer question batch response: %w
- failed to decode cancel question batch response: %w
- failed to make request: %w
- failed to decode response: %w
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/2b36b8e7a1d15866.
Report an issue: GitHub.