charmbracelet/crush · error
failed to decode agent status: %w
Error message
failed to decode agent status: %w
What it means
After a successful checkStatus, GetAgentInfo decodes the body into proto.AgentInfo. If the body is not valid JSON matching proto.AgentInfo (empty body, HTML from a proxy, schema drift), the decode fails and this error wraps the json error.
Source
Thrown at internal/client/proto.go:468
if rsp.StatusCode != http.StatusOK {
return fmt.Errorf("failed to clear session agent queued prompts: status code %d", rsp.StatusCode)
}
return nil
}
// GetAgentInfo retrieves the agent status for a workspace.
func (c *Client) GetAgentInfo(ctx context.Context, id string) (*proto.AgentInfo, error) {
rsp, err := c.get(ctx, fmt.Sprintf("/workspaces/%s/agent", id), nil, nil)
if err != nil {
return nil, fmt.Errorf("failed to get agent status: %w", err)
}
defer rsp.Body.Close()
if err := checkStatus(rsp); err != nil {
return nil, fmt.Errorf("failed to get agent status: %w", err)
}
var info proto.AgentInfo
if err := json.NewDecoder(rsp.Body).Decode(&info); err != nil {
return nil, fmt.Errorf("failed to decode agent status: %w", err)
}
return &info, nil
}
// UpdateAgent triggers an agent model update on the server.
func (c *Client) UpdateAgent(ctx context.Context, id string) error {
rsp, err := c.post(ctx, fmt.Sprintf("/workspaces/%s/agent/update", id), nil, nil, nil)
if err != nil {
return fmt.Errorf("failed to update agent: %w", err)
}
defer rsp.Body.Close()
if rsp.StatusCode != http.StatusOK {
return fmt.Errorf("failed to update agent: status code %d", rsp.StatusCode)
}
return nil
}
// SendMessage sends a message to the agent for a workspace.View on GitHub (pinned to 7944b8e522)
Solutions
- Align client and server versions so proto.AgentInfo matches
- Log the raw response body on decode failure
- Check for proxies altering the response
- Update the generated proto types if the server schema changed
Example fix
// before
var info proto.AgentInfo
if err := json.NewDecoder(rsp.Body).Decode(&info); err != nil {
return nil, fmt.Errorf("failed to decode agent status: %w", err)
}
// after
body, _ := io.ReadAll(rsp.Body)
var info proto.AgentInfo
if err := json.Unmarshal(body, &info); err != nil {
return nil, fmt.Errorf("decode agent status %q: %w", body, err)
} Defensive patterns
Strategy: type-guard
Type guard
func decodeAgentInfo(r io.Reader) (*proto.AgentInfo, error) {
body, err := io.ReadAll(r)
if err != nil { return nil, err }
if len(bytes.TrimSpace(body)) == 0 { return nil, errors.New("empty agent status body") }
var info proto.AgentInfo
if err := json.Unmarshal(body, &info); err != nil {
return nil, fmt.Errorf("agent status body %q: %w", body, err)
}
return &info, nil
} Try / catch
info, err := client.GetAgentInfo(ctx, wsID)
if err != nil && strings.Contains(err.Error(), "failed to decode agent status") {
// do not retry: schema mismatch — log body and check versions
} Prevention
- Keep proto.AgentInfo definitions identical on client and server
- Capture the raw body on decode failure
- Block proxies/HTML error pages from API routes
- Add golden tests for the agent status response shape
When it happens
Trigger: Server returns 200 with a body that fails to unmarshal into proto.AgentInfo — version mismatch where fields changed types, empty response, or proxy-injected content.
Common situations: Client and server built from different versions after a proto.AgentInfo schema change; misbehaving reverse proxy; server returning null or a wrapped object instead of the expected struct.
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 import copilot response: %w
- failed to decode LSP diagnostics: %w
- failed to decode LSPs: %w
- failed to decode MCP states: %w
- failed to decode session agent queued prompts: %w
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/81530c0a4f6572a9.
Report an issue: GitHub.