{"record":{"id":"6c3a74ea0e1c91b6","repo":"sipeed/picoclaw","slug":"failed-to-load-credentials-v","errorCode":null,"errorMessage":"failed to load credentials: %v","messagePattern":"failed to load credentials: (.+?)","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"web/backend/api/oauth.go","lineNumber":135,"sourceCode":"}\n\n// registerOAuthRoutes binds OAuth login/logout endpoints to the ServeMux.\nfunc (h *Handler) registerOAuthRoutes(mux *http.ServeMux) {\n\tmux.HandleFunc(\"GET /api/oauth/providers\", h.handleListOAuthProviders)\n\tmux.HandleFunc(\"POST /api/oauth/login\", h.handleOAuthLogin)\n\tmux.HandleFunc(\"GET /api/oauth/flows/{id}\", h.handleGetOAuthFlow)\n\tmux.HandleFunc(\"POST /api/oauth/flows/{id}/poll\", h.handlePollOAuthFlow)\n\tmux.HandleFunc(\"POST /api/oauth/logout\", h.handleOAuthLogout)\n\tmux.HandleFunc(\"GET /oauth/callback\", h.handleOAuthCallback)\n}\n\nfunc (h *Handler) handleListOAuthProviders(w http.ResponseWriter, r *http.Request) {\n\tprovidersResp := make([]oauthProviderStatus, 0, len(oauthProviderOrder))\n\n\tfor _, provider := range oauthProviderOrder {\n\t\tcred, err := oauthGetCredential(provider)\n\t\tif err != nil {\n\t\t\thttp.Error(w, fmt.Sprintf(\"failed to load credentials: %v\", err), http.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\n\t\titem := oauthProviderStatus{\n\t\t\tProvider:    provider,\n\t\t\tDisplayName: oauthProviderLabels[provider],\n\t\t\tMethods:     oauthProviderMethods[provider],\n\t\t\tStatus:      \"not_logged_in\",\n\t\t}\n\t\tif cred != nil {\n\t\t\titem.LoggedIn = true\n\t\t\titem.AuthMethod = cred.AuthMethod\n\t\t\titem.AccountID = cred.AccountID\n\t\t\titem.Email = cred.Email\n\t\t\titem.ProjectID = cred.ProjectID\n\t\t\tif !cred.ExpiresAt.IsZero() {\n\t\t\t\titem.ExpiresAt = cred.ExpiresAt.Format(time.RFC3339)\n\t\t\t}","sourceCodeStart":117,"sourceCodeEnd":153,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/web/backend/api/oauth.go#L117-L153","documentation":"Returned by GET /api/oauth/providers (handleListOAuthProviders) when oauthGetCredential → auth.GetCredential(provider) errors while loading the credential store (a JSON auth file on disk, written atomically with 0600 perms). Important distinction: a provider that is simply not logged in returns a nil credential with no error; this 500 means the store file itself could not be read or parsed (%v has the cause).","triggerScenarios":"GET /api/oauth/providers while the auth credentials file is missing-but-unreadable, has wrong permissions, or is corrupted JSON. Any single provider's store load failure aborts the whole listing — the loop returns on first error.","commonSituations":"Auth file created by root, then backend restarted as another user (permission denied); partial write from a crash left truncated JSON; file locked or SELinux-denied on hardened systems; auth dir moved/deleted.","solutions":["Read %v: 'permission denied' → chown/chmod the auth file (0600) and its directory to the backend user","'unexpected end of JSON input' → the store is truncated; rename the corrupt file aside, restart, and re-login providers","Verify the auth file path the backend uses (authFilePath()) exists in the environment the service actually runs in"],"exampleFix":"# before\n$ ls -l ~/.picoclaw/auth.json\n-rw------- 1 root root 468\n(backend runs as 'beagle')\n\n# after\n$ chown beagle:beagle ~/.picoclaw/auth.json && chmod 600 ~/.picoclaw/auth.json","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  const res = await fetch('/api/oauth/providers');\n  if (res.status === 500 && (await res.text()).includes('load credentials')) {\n    /* credential store unreadable — surface to admin (permissions/corrupt auth file), do not retry */\n  }\n} catch (e) { /* transport */ }","preventionTips":["Keep the auth credentials file owned by the backend user with 0600 perms","Never hand-edit the auth store; use the login/logout endpoints","Back up the auth file before user/service changes; alert on any 500 from this read-only endpoint"],"tags":["oauth","credentials","filesystem","permissions","server"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}