{"record":{"id":"d2567ab1a0b91e65","repo":"charmbracelet/crush","slug":"failed-to-decode-init-prompt-response-w","errorCode":null,"errorMessage":"failed to decode init prompt response: %w","messagePattern":"failed to decode init prompt response: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/client/config.go","lineNumber":214,"sourceCode":"\treturn nil\n}\n\n// GetInitializePrompt retrieves the initialization prompt from the\n// server.\nfunc (c *Client) GetInitializePrompt(ctx context.Context, id string) (string, error) {\n\trsp, err := c.get(ctx, fmt.Sprintf(\"/workspaces/%s/project/init-prompt\", id), nil, nil)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to get init prompt: %w\", err)\n\t}\n\tdefer rsp.Body.Close()\n\tif rsp.StatusCode != http.StatusOK {\n\t\treturn \"\", fmt.Errorf(\"failed to get init prompt: status code %d\", rsp.StatusCode)\n\t}\n\tvar result struct {\n\t\tPrompt string `json:\"prompt\"`\n\t}\n\tif err := json.NewDecoder(rsp.Body).Decode(&result); err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to decode init prompt response: %w\", err)\n\t}\n\treturn result.Prompt, nil\n}\n\n// ListSkills retrieves the visible skills for a workspace.\nfunc (c *Client) ListSkills(ctx context.Context, id string) ([]proto.SkillInfo, error) {\n\trsp, err := c.get(ctx, fmt.Sprintf(\"/workspaces/%s/skills\", id), nil, nil)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to list skills: %w\", err)\n\t}\n\tdefer rsp.Body.Close()\n\tif rsp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"failed to list skills: status code %d\", rsp.StatusCode)\n\t}\n\tvar skills []proto.SkillInfo\n\tif err := json.NewDecoder(rsp.Body).Decode(&skills); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to decode skills: %w\", err)\n\t}","sourceCodeStart":196,"sourceCodeEnd":232,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/client/config.go#L196-L232","documentation":"After a 200 response, GetInitializePrompt decodes the body expecting {\"prompt\": string}. Any decode failure (empty body, non-JSON, wrong shape) raises this error. It signals a server/client response-contract mismatch, not a connectivity issue, and propagates to InitializePrompt.","triggerScenarios":"Calling GetInitializePrompt when the 200 body is not decodable into {prompt: string}: empty body, HTML injected by a proxy or auth wall, or a server version returning a renamed/nested prompt field.","commonSituations":"Client/server version skew after the init-prompt payload changed; reverse proxy or SSO login page returned instead of JSON; compression middleware misconfiguration; server returning 200 with an empty body on internal errors.","solutions":["Log or capture the raw response body to see the actual payload","Align client and server versions for the init-prompt contract","Reconfigure or bypass proxies/SSO walls that return HTML with 200","Add a server-side guard so internal errors never return 200 with a non-JSON body"],"exampleFix":"// before\nprompt, err := client.GetInitializePrompt(ctx, id)\nif err != nil {\n    return err\n}\n// after\nprompt, err := client.GetInitializePrompt(ctx, id)\nif err != nil {\n    log.Printf(\"init-prompt decode failed; check server version/proxy: %v\", err)\n    return err\n}","handlingStrategy":"type-guard","validationCode":"// Verify the endpoint returns JSON before decoding\ntestResp, err := http.Get(client.BaseURL() + \"/healthz\")\nif err != nil {\n    return err\n}\nct := testResp.Header.Get(\"Content-Type\")\ntestResp.Body.Close()\nif !strings.HasPrefix(ct, \"application/json\") {\n    return fmt.Errorf(\"non-JSON API responses detected (proxy/auth wall?)\")\n}","typeGuard":"func looksLikeJSON(b []byte) bool {\n    t := bytes.TrimSpace(b)\n    return len(t) > 0 && (t[0] == '{' || t[0] == '[')\n}","tryCatchPattern":"prompt, err := c.GetInitializePrompt(ctx, id)\nif err != nil {\n    if strings.Contains(err.Error(), \"failed to decode\") {\n        // contract mismatch: fall back to a local default prompt\n        return defaultInitPrompt, nil\n    }\n    return err\n}","preventionTips":["Golden-test the init-prompt response payload on the server side","Keep client and server releases in lockstep when the payload schema changes","Detect and reject HTML/auth-wall responses at the proxy layer","Ship a local fallback prompt so onboarding survives contract drift"],"tags":["json","http-client","api-contract"],"backgroundTag":"json-decode-failed","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}