charmbracelet/crush · error

failed to decode shell command response: %w

Error message

failed to decode shell command response: %w

What it means

After a 200 response, RunShellCommand decodes the body into proto.ShellCommandResponse. This error wraps any JSON decode failure (empty body, truncated response, wrong content type, schema mismatch) via %w. The command may actually have run server-side even though the result could not be parsed.

Source

Thrown at internal/client/proto.go:541

	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)
	}
	defer rsp.Body.Close()
	if rsp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("failed to get session agent info: status code %d", rsp.StatusCode)
	}
	var info proto.AgentSession
	if err := json.NewDecoder(rsp.Body).Decode(&info); err != nil {
		return nil, fmt.Errorf("failed to decode session agent info: %w", err)
	}

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Check for client/server version skew and align versions — schema mismatch is the most common cause.
  2. Inspect the raw response body (debug proxy or server logs) to see what was actually returned.
  3. Treat the command as possibly-executed; avoid blind retries of non-idempotent commands.
  4. Verify no intermediary proxy is modifying or truncating the response body.

Example fix

// before: unsafe retry of any decode failure
if err != nil { retry(cmd) }
// after: decode failure may mean the command already ran
resp, err := client.RunShellCommand(ctx, wsID, sid, cmd, w)
if err != nil && strings.Contains(err.Error(), "failed to decode shell command response") {
    log.Printf("result lost for %q; command may have executed", cmd)
    return err
}
Defensive patterns

Strategy: type-guard

Validate before calling

// Go: check client/server protocol version compatibility before calls
if clientVersion != serverAPIVersion {
    return fmt.Errorf("client %s vs server %s: response schema may mismatch", clientVersion, serverAPIVersion)
}

Type guard

// Go: identify decode failures specifically
func isDecodeFailure(err error) bool {
    return strings.Contains(err.Error(), "failed to decode shell command response")
}

Try / catch

resp, err := client.RunShellCommand(ctx, wsID, sid, cmd, w)
if err != nil {
    if isDecodeFailure(err) {
        // command may have executed; verify out-of-band before retrying
        return fmt.Errorf("result unreadable for %q; check server state before rerunning", cmd)
    }
    return err
}

Prevention

When it happens

Trigger: Server returns 200 with an empty, truncated, or non-JSON body, or a ShellCommandResponse schema the client cannot unmarshal (version skew between client and server types).

Common situations: Proxy gzipping/stripping the body; server/client version mismatch after an upgrade; server crash after starting the command but before writing the response.

Understand the failure class

Related errors


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