{"record":{"id":"ff213e7da42cbfe0","repo":"charmbracelet/crush","slug":"failed-to-decode-shell-command-response-w","errorCode":null,"errorMessage":"failed to decode shell command response: %w","messagePattern":"failed to decode shell command response: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/client/proto.go","lineNumber":541,"sourceCode":"\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}\n\tdefer rsp.Body.Close()\n\tif rsp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"failed to get session agent info: status code %d\", rsp.StatusCode)\n\t}\n\tvar info proto.AgentSession\n\tif err := json.NewDecoder(rsp.Body).Decode(&info); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to decode session agent info: %w\", err)\n\t}","sourceCodeStart":523,"sourceCodeEnd":559,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/client/proto.go#L523-L559","documentation":"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.","triggerScenarios":"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).","commonSituations":"Proxy gzipping/stripping the body; server/client version mismatch after an upgrade; server crash after starting the command but before writing the response.","solutions":["Check for client/server version skew and align versions — schema mismatch is the most common cause.","Inspect the raw response body (debug proxy or server logs) to see what was actually returned.","Treat the command as possibly-executed; avoid blind retries of non-idempotent commands.","Verify no intermediary proxy is modifying or truncating the response body."],"exampleFix":"// before: unsafe retry of any decode failure\nif err != nil { retry(cmd) }\n// after: decode failure may mean the command already ran\nresp, err := client.RunShellCommand(ctx, wsID, sid, cmd, w)\nif err != nil && strings.Contains(err.Error(), \"failed to decode shell command response\") {\n    log.Printf(\"result lost for %q; command may have executed\", cmd)\n    return err\n}","handlingStrategy":"type-guard","validationCode":"// Go: check client/server protocol version compatibility before calls\nif clientVersion != serverAPIVersion {\n    return fmt.Errorf(\"client %s vs server %s: response schema may mismatch\", clientVersion, serverAPIVersion)\n}","typeGuard":"// Go: identify decode failures specifically\nfunc isDecodeFailure(err error) bool {\n    return strings.Contains(err.Error(), \"failed to decode shell command response\")\n}","tryCatchPattern":"resp, err := client.RunShellCommand(ctx, wsID, sid, cmd, w)\nif err != nil {\n    if isDecodeFailure(err) {\n        // command may have executed; verify out-of-band before retrying\n        return fmt.Errorf(\"result unreadable for %q; check server state before rerunning\", cmd)\n    }\n    return err\n}","preventionTips":["Do not blindly retry after a decode failure — the command may have already executed.","Pin matching client/server versions; deploy them together.","Disable any proxy that rewrites, gzips, or truncates response bodies.","Inspect raw responses once (debug proxy) to identify schema drift early."],"tags":["json-decode","shell-execution","version-skew"],"backgroundTag":"response-decode-failed","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}