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.Error

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Confirm the server is running and the base URL/port are correct.
  2. Test connectivity with a simple GET (e.g. GetAgentSessionInfo) to isolate transport issues.
  3. Retry with backoff for transient network failures, especially in CI pipelines.
  4. 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

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


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