{"record":{"id":"316dc698033f0ba5","repo":"charmbracelet/crush","slug":"failed-to-run-shell-command-w","errorCode":null,"errorMessage":"failed to run shell command: %w","messagePattern":"failed to run shell command: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/client/proto.go","lineNumber":533,"sourceCode":"// when the body is empty or cannot be decoded into a proto.Error\n// with a non-empty message, letting callers fall back to a\n// status-only error.\nfunc decodeErrorMessage(body io.Reader) string {\n\tvar e proto.Error\n\tif err := json.NewDecoder(body).Decode(&e); err != nil {\n\t\treturn \"\"\n\t}\n\treturn e.Message\n}\n\n// RunShellCommand runs a shell command in the workspace without triggering the agent.\nfunc (c *Client) RunShellCommand(ctx context.Context, id, sessionID, command string, termWidth int) (proto.ShellCommandResponse, error) {\n\trsp, err := c.post(ctx, fmt.Sprintf(\"/workspaces/%s/agent/sessions/%s/shell\", id, sessionID), nil, jsonBody(proto.ShellCommandRequest{\n\t\tCommand:   command,\n\t\tTermWidth: termWidth,\n\t}), http.Header{\"Content-Type\": []string{\"application/json\"}})\n\tif err != nil {\n\t\treturn proto.ShellCommandResponse{}, fmt.Errorf(\"failed to run shell command: %w\", err)\n\t}\n\tdefer rsp.Body.Close()\n\tif rsp.StatusCode != http.StatusOK {\n\t\treturn proto.ShellCommandResponse{}, fmt.Errorf(\"failed to run shell command: status code %d\", rsp.StatusCode)\n\t}\n\tvar resp proto.ShellCommandResponse\n\tif err := json.NewDecoder(rsp.Body).Decode(&resp); err != nil {\n\t\treturn proto.ShellCommandResponse{}, fmt.Errorf(\"failed to decode shell command response: %w\", err)\n\t}\n\treturn resp, nil\n}\n\n// GetAgentSessionInfo retrieves the agent session info for a workspace.\nfunc (c *Client) GetAgentSessionInfo(ctx context.Context, id string, sessionID string) (*proto.AgentSession, error) {\n\trsp, err := c.get(ctx, fmt.Sprintf(\"/workspaces/%s/agent/sessions/%s\", id, sessionID), nil, nil)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get session agent info: %w\", err)\n\t}","sourceCodeStart":515,"sourceCodeEnd":551,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/client/proto.go#L515-L551","documentation":"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.","triggerScenarios":"Calling RunShellCommand when the server is unreachable, the connection drops, the request times out, or the JSON body (command + termWidth) cannot be sent.","commonSituations":"Running remote shell commands against a server that just exited; network partition mid-session; very large output/long-running command hitting a client timeout.","solutions":["Verify server availability and base URL before invoking.","Check the wrapped cause to distinguish timeout vs refused connection.","For long-running commands, confirm the client timeout exceeds the expected command duration.","Retry transient network failures with backoff."],"exampleFix":"// before\nresp, err := client.RunShellCommand(ctx, wsID, sid, cmd, width)\n// after\nresp, err := client.RunShellCommand(ctx, wsID, sid, cmd, width)\nif err != nil {\n    if errors.Is(err, context.DeadlineExceeded) {\n        return fmt.Errorf(\"command %q timed out: %w\", cmd, err)\n    }\n    return err\n}","handlingStrategy":"try-catch","validationCode":"// Go: guard inputs and context before running a remote command\nif command == \"\" { return fmt.Errorf(\"command is empty\") }\nif termWidth <= 0 { termWidth = 80 }\nif err := ctx.Err(); err != nil { return err }","typeGuard":"// Go: classify the wrapped transport failure\nfunc classifyTransport(err error) string {\n    switch {\n    case errors.Is(err, context.DeadlineExceeded): return \"timeout\"\n    case errors.Is(err, syscall.ECONNREFUSED): return \"refused\"\n    default: return \"other\"\n    }\n}","tryCatchPattern":"resp, err := client.RunShellCommand(ctx, wsID, sid, cmd, width)\nif err != nil {\n    if classifyTransport(err) == \"timeout\" {\n        log.Printf(\"command %q may still be running server-side\", cmd)\n    }\n    return err\n}","preventionTips":["Set the context timeout well above expected command runtime.","Confirm the session is alive before executing remote commands.","On timeout, check server-side process state — the command may have run.","Keep client/server protocol versions aligned."],"tags":["network","http-client","shell-execution"],"backgroundTag":"http-request-failed","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}