{"record":{"id":"fa60084e8d9755c9","repo":"charmbracelet/crush","slug":"failed-to-connect-to-provider-s-s","errorCode":null,"errorMessage":"failed to connect to provider %s: %s","messagePattern":"failed to connect to provider (.+?): (.+?)","errorType":"http","errorClass":null,"httpStatus":401,"severity":"error","filePath":"internal/config/config.go","lineNumber":1038,"sourceCode":"\t\treturn fmt.Errorf(\"failed to create request for provider %s: %w\", c.ID, err)\n\t}\n\tfor k, v := range headers {\n\t\treq.Header.Set(k, v)\n\t}\n\tfor k, v := range c.ExtraHeaders {\n\t\treq.Header.Set(k, v)\n\t}\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create request for provider %s: %w\", c.ID, err)\n\t}\n\tdefer resp.Body.Close()\n\n\tswitch providerID {\n\tcase catwalk.InferenceProviderZAI:\n\t\tif resp.StatusCode == http.StatusUnauthorized {\n\t\t\treturn fmt.Errorf(\"failed to connect to provider %s: %s\", c.ID, resp.Status)\n\t\t}\n\tdefault:\n\t\tif resp.StatusCode != http.StatusOK {\n\t\t\treturn fmt.Errorf(\"failed to connect to provider %s: %s\", c.ID, resp.Status)\n\t\t}\n\t}\n\treturn nil\n}\n\n// resolveEnvs expands every value in envs through the given resolver\n// and returns a fresh \"KEY=value\" slice sorted by key. The input map is\n// not mutated. On the first resolution failure it returns nil and an\n// error identifying the offending variable; the inner resolver error is\n// already sanitized by ResolveValue and is wrapped with %w.\nfunc resolveEnvs(envs map[string]string, r VariableResolver) ([]string, error) {\n\tif len(envs) == 0 {\n\t\treturn nil, nil\n\t}","sourceCodeStart":1020,"sourceCodeEnd":1056,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/config/config.go#L1020-L1056","documentation":"Returned when the Z.AI provider probe responds with HTTP 401 Unauthorized during API-key validation. For ZAI, a 401 is treated as a definitive key failure (other statuses are tolerated as inconclusive), so the response status text is surfaced to the user.","triggerScenarios":"Calling API-key validation for InferenceProviderZAI and receiving a 401 response — the configured Z.AI API key is missing, expired, revoked, or malformed.","commonSituations":"Rotated or deleted Z.AI key still referenced in config; key set for the wrong Z.AI endpoint (international vs China); Bearer header not being sent because the key resolved to an empty string.","solutions":["Generate a fresh API key from the Z.AI console and update it in crushrc/crush.json.","Confirm the key matches the endpoint region you configured (api.z.ai vs open.bigmodel.cn).","Verify the env variable holding the key is actually set and non-empty for the process.","Test the key directly: curl -H \"Authorization: Bearer $KEY\" https://api.z.ai/... to see the same 401."],"exampleFix":"// before (crushrc)\nprovider zai\n  api_key \"{{ env:OLD_REVOKED_KEY }}\"\nend\n// after\nexport ZAI_API_KEY=<new key from Z.AI console>\nprovider zai\n  api_key \"{{ env:ZAI_API_KEY }}\"\nend","handlingStrategy":"validation","validationCode":"if zaiKey == \"\" {\n    return errors.New(\"ZAI_API_KEY is not set; obtain one from the Z.AI console before validating\")\n}\n// optional live pre-check:\nresp, err := http.Get(\"https://api.z.ai/v1/models\") // expect non-401","typeGuard":"func isAuthFailure(statusCode int) bool { return statusCode == http.StatusUnauthorized || statusCode == http.StatusForbidden }","tryCatchPattern":"if err := validateProviderKey(ctx, zaiCfg); err != nil {\n    if strings.Contains(err.Error(), \"401\") {\n        openKeyConsole(\"https://z.ai\") // guide user to rotate key\n        return nil\n    }\n    return err\n}","preventionTips":["Rotate keys on a schedule and update config immediately after revocation.","Keep separate keys for international vs China Z.AI endpoints.","Verify the env var is exported in the environment where Crush runs."],"tags":["auth","api-key","http-401","zai"],"backgroundTag":"http-401-unauthorized","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}