charmbracelet/crush · error
failed to send message to agent: %w
Error message
failed to send message to agent: %w
What it means
SendMessage posts a JSON message to /workspaces/{id}/agent/sessions/messages. This error wraps a transport failure from c.post (unreachable server, connection reset, timeout, request serialization failure) before any response is read. The underlying cause is preserved with %w.
Source
Thrown at internal/client/proto.go:501
return nil
}
// SendMessage sends a message to the agent for a workspace.
//
// 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.ErrorView on GitHub (pinned to 7944b8e522)
Solutions
- Confirm the server is running and the base URL/port are correct.
- Test connectivity with a simple GET (e.g. GetAgentSessionInfo) to isolate transport issues.
- Retry with backoff for transient network failures, especially in CI pipelines.
- Check the wrapped cause (%w) to distinguish timeout vs connection-refused.
Example fix
// before
err := client.SendMessage(ctx, wsID, sessionID, runID, prompt, nil)
// after
var lastErr error
for i := 0; i < 3; i++ {
if lastErr = client.SendMessage(ctx, wsID, sessionID, runID, prompt, nil); lastErr == nil {
return nil
}
time.Sleep(time.Duration(1<<i) * time.Second)
}
return lastErr Defensive patterns
Strategy: retry
Validate before calling
// Go: validate inputs before sending
if prompt == "" { return fmt.Errorf("prompt is empty") }
if workspaceID == "" || sessionID == "" {
return fmt.Errorf("workspace and session ids are required")
} Type guard
// Go: distinguish transport errors from status errors
func isTransportError(err error) bool {
return err != nil && !strings.Contains(err.Error(), "status code")
} Try / catch
err := client.SendMessage(ctx, wsID, sid, runID, prompt, attachments)
for retry := 0; err != nil && isTransportError(err) && retry < 3; retry++ {
time.Sleep(time.Duration(1<<retry) * time.Second)
err = client.SendMessage(ctx, wsID, sid, runID, prompt, attachments)
}
if err != nil { return err } Prevention
- Run non-interactive jobs only after a connectivity smoke test.
- Set a context timeout larger than the longest expected send (attachments can be large).
- Prefer idempotent sends or include a client request ID the server can dedupe.
- Keep the client and server on compatible versions.
When it happens
Trigger: Calling SendMessage (e.g. from runNonInteractive) when the server is down, the connection drops mid-request, the request times out, or the request body cannot be constructed/sent.
Common situations: Non-interactive runs against a stopped server; large prompts/attachments timing out; flaky network in CI; proxy/firewall blocking POSTs.
Understand the failure class
Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.
Related errors
- failed to make request: %w
- failed to refresh OAuth token: %w
- failed to check project init: %w
- failed to mark project initialized: %w
- failed to get init prompt: %w
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/113e060cdb35cec3.
Report an issue: GitHub.