{"record":{"id":"3bf51f6016d6c382","repo":"sipeed/picoclaw","slug":"token-login-failed-v","errorCode":null,"errorMessage":"token login failed: %v","messagePattern":"token login failed: (.+?)","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"web/backend/api/oauth.go","lineNumber":221,"sourceCode":"\t\t)\n\t\treturn\n\t}\n\n\tswitch method {\n\tcase oauthMethodToken:\n\t\ttoken := strings.TrimSpace(req.Token)\n\t\tif token == \"\" {\n\t\t\thttp.Error(w, \"token is required\", http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\n\t\tcred := &auth.AuthCredential{\n\t\t\tAccessToken: token,\n\t\t\tProvider:    provider,\n\t\t\tAuthMethod:  oauthMethodToken,\n\t\t}\n\t\tif err := h.persistCredentialAndConfig(provider, oauthMethodToken, cred); err != nil {\n\t\t\thttp.Error(w, fmt.Sprintf(\"token login failed: %v\", err), http.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\t_ = json.NewEncoder(w).Encode(map[string]any{\n\t\t\t\"status\":   \"ok\",\n\t\t\t\"provider\": provider,\n\t\t\t\"method\":   method,\n\t\t})\n\t\treturn\n\n\tcase oauthMethodDeviceCode:\n\t\tcfg := auth.OpenAIOAuthConfig()\n\t\tinfo, err := oauthRequestDeviceCode(cfg)\n\t\tif err != nil {\n\t\t\thttp.Error(w, fmt.Sprintf(\"failed to request device code: %v\", err), http.StatusInternalServerError)\n\t\t\treturn\n\t\t}","sourceCodeStart":203,"sourceCodeEnd":239,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/web/backend/api/oauth.go#L203-L239","documentation":"Returned as HTTP 500 by POST /api/oauth/login (token method) when persistCredentialAndConfig fails. That function does two things that can error: auth.SetCredential (load + save the credential store file on disk) and syncProviderAuthMethod (load + save picoclaw's config.json at h.configPath). The wrapped %v tells you which stage failed (\"saving credential: ...\" or \"syncing provider auth config: ...\"). The token itself is never validated against the provider at this step, so this error is always a local persistence failure, not a bad token.","triggerScenarios":"POST /api/oauth/login {\"provider\":\"openai\",\"method\":\"token\",\"token\":\"sk-...\"} when the credential store file is unreadable/corrupt (SetCredential → LoadStore fails), the credentials file or config.json is read-only, or the directory is not writable (SaveStore/SaveConfig fail).","commonSituations":"Running the backend as a different user than the one who owns ~/.config/picoclaw (permission denied); a partially written or hand-edited credential store with invalid JSON; disk full; config.json mounted read-only in a container.","solutions":["Read the wrapped detail: \"saving credential: ...\" means the auth store, \"syncing provider auth config: ...\" means config.json.","Check the config file path the backend was started with (h.configPath) is readable and writable by the backend process user.","Inspect the credential store file (auth store location, e.g. under the picoclaw config dir) for corruption; if it is not valid JSON, rename it away and retry login to recreate it.","Fix ownership/permissions (chown/chmod) or free disk space, then retry the login POST."],"exampleFix":"# before: backend user cannot write config\n$ ls -l /etc/picoclaw/config.json\n-rw-r--r-- 1 root root ... config.json   # 500 token login failed: syncing provider auth config: ...\n\n# after\n$ sudo chown $(whoami) /etc/picoclaw/config.json && curl -X POST http://localhost:8080/api/oauth/login -d '{\"provider\":\"openai\",\"method\":\"token\",\"token\":\"sk-...\"}'","handlingStrategy":"try-catch","validationCode":"await fs.access(configPath, fs.constants.W_OK | fs.constants.R_OK); // fail fast if config.json is not writable\nconst st = await fs.stat(credsFile); // ensure the credential store exists and is readable\nawait fs.access(credsFile, fs.constants.W_OK);","typeGuard":null,"tryCatchPattern":"const res = await fetch('/api/oauth/login', {...});\nif (res.status === 500) {\n  const { message } = await res.json();\n  if (message?.startsWith('token login failed')) {\n    // local persistence failure — surface filesystem detail, do NOT retry with the same broken state\n    throw new Error(`Login could not be saved: ${message}`);\n  }\n  throw new Error(message);\n}","preventionTips":["Run the backend as the user that owns the config dir and credential store.","Validate config.json and the credentials file parse as JSON before starting login flows.","A 500 here is never a bad token — do not ask the user to re-paste the token; fix the filesystem first."],"tags":["oauth","http-500","credentials","filesystem","persistence"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}