sipeed/picoclaw · error

token login failed: %v

Error message

token login failed: %v

What it means

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.

Source

Thrown at web/backend/api/oauth.go:221

		)
		return
	}

	switch method {
	case oauthMethodToken:
		token := strings.TrimSpace(req.Token)
		if token == "" {
			http.Error(w, "token is required", http.StatusBadRequest)
			return
		}

		cred := &auth.AuthCredential{
			AccessToken: token,
			Provider:    provider,
			AuthMethod:  oauthMethodToken,
		}
		if err := h.persistCredentialAndConfig(provider, oauthMethodToken, cred); err != nil {
			http.Error(w, fmt.Sprintf("token login failed: %v", err), http.StatusInternalServerError)
			return
		}

		w.Header().Set("Content-Type", "application/json")
		_ = json.NewEncoder(w).Encode(map[string]any{
			"status":   "ok",
			"provider": provider,
			"method":   method,
		})
		return

	case oauthMethodDeviceCode:
		cfg := auth.OpenAIOAuthConfig()
		info, err := oauthRequestDeviceCode(cfg)
		if err != nil {
			http.Error(w, fmt.Sprintf("failed to request device code: %v", err), http.StatusInternalServerError)
			return
		}

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Read the wrapped detail: "saving credential: ..." means the auth store, "syncing provider auth config: ..." means config.json.
  2. Check the config file path the backend was started with (h.configPath) is readable and writable by the backend process user.
  3. 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.
  4. Fix ownership/permissions (chown/chmod) or free disk space, then retry the login POST.

Example fix

# before: backend user cannot write config
$ ls -l /etc/picoclaw/config.json
-rw-r--r-- 1 root root ... config.json   # 500 token login failed: syncing provider auth config: ...

# after
$ sudo chown $(whoami) /etc/picoclaw/config.json && curl -X POST http://localhost:8080/api/oauth/login -d '{"provider":"openai","method":"token","token":"sk-..."}'
Defensive patterns

Strategy: try-catch

Validate before calling

await fs.access(configPath, fs.constants.W_OK | fs.constants.R_OK); // fail fast if config.json is not writable
const st = await fs.stat(credsFile); // ensure the credential store exists and is readable
await fs.access(credsFile, fs.constants.W_OK);

Try / catch

const res = await fetch('/api/oauth/login', {...});
if (res.status === 500) {
  const { message } = await res.json();
  if (message?.startsWith('token login failed')) {
    // local persistence failure — surface filesystem detail, do NOT retry with the same broken state
    throw new Error(`Login could not be saved: ${message}`);
  }
  throw new Error(message);
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/3bf51f6016d6c382. Report an issue: GitHub.