charmbracelet/crush · error

failed to run shell command: %w

Error message

failed to run shell command: %w

What it means

RunShellCommand posts to /workspaces/{id}/agent/sessions/{sid}/shell to execute a command on the agent side. This error wraps a transport failure from c.post (unreachable server, connection reset, timeout, request-build failure) before the response status is checked, with the cause preserved via %w.

Source

Thrown at internal/client/proto.go:533

// 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
}

// RunShellCommand runs a shell command in the workspace without triggering the agent.
func (c *Client) RunShellCommand(ctx context.Context, id, sessionID, command string, termWidth int) (proto.ShellCommandResponse, error) {
	rsp, err := c.post(ctx, fmt.Sprintf("/workspaces/%s/agent/sessions/%s/shell", id, sessionID), nil, jsonBody(proto.ShellCommandRequest{
		Command:   command,
		TermWidth: termWidth,
	}), http.Header{"Content-Type": []string{"application/json"}})
	if err != nil {
		return proto.ShellCommandResponse{}, fmt.Errorf("failed to run shell command: %w", err)
	}
	defer rsp.Body.Close()
	if rsp.StatusCode != http.StatusOK {
		return proto.ShellCommandResponse{}, fmt.Errorf("failed to run shell command: status code %d", rsp.StatusCode)
	}
	var resp proto.ShellCommandResponse
	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)
	}

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Verify server availability and base URL before invoking.
  2. Check the wrapped cause to distinguish timeout vs refused connection.
  3. For long-running commands, confirm the client timeout exceeds the expected command duration.
  4. Retry transient network failures with backoff.

Example fix

// before
resp, err := client.RunShellCommand(ctx, wsID, sid, cmd, width)
// after
resp, err := client.RunShellCommand(ctx, wsID, sid, cmd, width)
if err != nil {
    if errors.Is(err, context.DeadlineExceeded) {
        return fmt.Errorf("command %q timed out: %w", cmd, err)
    }
    return err
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Go: guard inputs and context before running a remote command
if command == "" { return fmt.Errorf("command is empty") }
if termWidth <= 0 { termWidth = 80 }
if err := ctx.Err(); err != nil { return err }

Type guard

// Go: classify the wrapped transport failure
func classifyTransport(err error) string {
    switch {
    case errors.Is(err, context.DeadlineExceeded): return "timeout"
    case errors.Is(err, syscall.ECONNREFUSED): return "refused"
    default: return "other"
    }
}

Try / catch

resp, err := client.RunShellCommand(ctx, wsID, sid, cmd, width)
if err != nil {
    if classifyTransport(err) == "timeout" {
        log.Printf("command %q may still be running server-side", cmd)
    }
    return err
}

Prevention

When it happens

Trigger: Calling RunShellCommand when the server is unreachable, the connection drops, the request times out, or the JSON body (command + termWidth) cannot be sent.

Common situations: Running remote shell commands against a server that just exited; network partition mid-session; very large output/long-running command hitting a client timeout.

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/316dc698033f0ba5. Report an issue: GitHub.