charmbracelet/crush · error

failed to send message to agent: status code %d: %s

Error message

failed to send message to agent: status code %d: %s

What it means

SendMessage accepts 200 OK or 202 Accepted. Any other status triggers this error, which also appends the server-provided error message decoded from the response body via decodeErrorMessage (proto.Error). It is the most informative variant of the SendMessage failure because it includes server-side detail.

Source

Thrown at internal/client/proto.go:506

// When runID is non-empty it is echoed back on the resulting
// proto.RunComplete event, giving the caller a unique correlator
// for completion detection. Pass "" when the caller does not need
// to distinguish its own turn's terminal event from any concurrent
// turn on the same session (e.g. interactive TUI usage).
func (c *Client) SendMessage(ctx context.Context, id string, sessionID, runID, prompt string, attachments ...message.Attachment) error {
	rsp, err := c.post(ctx, fmt.Sprintf("/workspaces/%s/agent", id), nil, jsonBody(proto.AgentMessage{
		SessionID:   sessionID,
		RunID:       runID,
		Prompt:      prompt,
		Attachments: proto.AttachmentsFromMessage(attachments),
	}), http.Header{"Content-Type": []string{"application/json"}})
	if err != nil {
		return fmt.Errorf("failed to send message to agent: %w", err)
	}
	defer rsp.Body.Close()
	if rsp.StatusCode != http.StatusOK && rsp.StatusCode != http.StatusAccepted {
		if msg := decodeErrorMessage(rsp.Body); msg != "" {
			return fmt.Errorf("failed to send message to agent: status code %d: %s", rsp.StatusCode, msg)
		}
		return fmt.Errorf("failed to send message to agent: status code %d", rsp.StatusCode)
	}
	return nil
}

// decodeErrorMessage attempts to decode the response body as a
// proto.Error and returns its message. It returns an empty string
// when the body is empty or cannot be decoded into a proto.Error
// with a non-empty message, letting callers fall back to a
// status-only error.
func decodeErrorMessage(body io.Reader) string {
	var e proto.Error
	if err := json.NewDecoder(body).Decode(&e); err != nil {
		return ""
	}
	return e.Message
}

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Read the %s message suffix — it contains the server's own error explanation.
  2. For 404, verify the workspace and session IDs are current (re-fetch session info).
  3. For 401/403, refresh authentication before retrying.
  4. For 400, validate the prompt and attachments against the API expectations.

Example fix

// before
if err := client.SendMessage(ctx, wsID, sid, runID, prompt, nil); err != nil {
    return err
}
// after
if err := client.SendMessage(ctx, wsID, sid, runID, prompt, nil); err != nil {
    log.Printf("send failed: %v", err) // includes server message after 'status code N:'
    return err
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Go: verify session still exists before sending
if _, err := client.GetAgentSessionInfo(ctx, wsID, sid); err != nil {
    return fmt.Errorf("session %s unavailable before send: %w", sid, err)
}

Type guard

// Go: pull the server-provided message out of the composed error
func serverMessage(err error) string {
    s := err.Error()
    if i := strings.Index(s, "status code "); i >= 0 {
        if j := strings.Index(s[i:], ": "); j >= 0 {
            return s[i+j+2:]
        }
    }
    return ""
}

Try / catch

if err := client.SendMessage(ctx, wsID, sid, runID, prompt, nil); err != nil {
    if msg := serverMessage(err); msg != "" {
        log.Printf("server rejected message: %s", msg) // actionable server detail
    }
    return err
}

Prevention

When it happens

Trigger: Sending a message when the session/workspace no longer exists (404), auth is rejected (401/403), the request payload is rejected (400), or the agent handler errors (500) — and the server returned a parseable error body.

Common situations: Sending to a session ID from a previous run; expired token in long-lived non-interactive jobs; server validation rejecting empty prompts or malformed attachments.

Related errors


AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29). Data as JSON: /api/errors/df4a936316772d7f. Report an issue: GitHub.