sipeed/picoclaw · error
invalid JSON: %v
Error message
invalid JSON: %v
What it means
Returned by POST /api/oauth/login when json.Unmarshal of the body into {provider, method, token} fails; %v names the exact JSON defect (syntax, wrong types, trailing data). All three fields are strings; extra fields are ignored, so the failure is always malformed JSON rather than schema mismatch.
Source
Thrown at web/backend/api/oauth.go:187
"providers": providersResp,
})
}
func (h *Handler) handleOAuthLogin(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(io.LimitReader(r.Body, 1<<20))
if err != nil {
http.Error(w, "failed to read request body", http.StatusBadRequest)
return
}
defer r.Body.Close()
var req struct {
Provider string `json:"provider"`
Method string `json:"method"`
Token string `json:"token"`
}
if err = json.Unmarshal(body, &req); err != nil {
http.Error(w, fmt.Sprintf("invalid JSON: %v", err), http.StatusBadRequest)
return
}
provider, err := normalizeOAuthProvider(req.Provider)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
method := strings.ToLower(strings.TrimSpace(req.Method))
if !isOAuthMethodSupported(provider, method) {
http.Error(
w,
fmt.Sprintf("unsupported login method %q for provider %q", method, provider),
http.StatusBadRequest,
)
return
}View on GitHub (pinned to 49183d7e8d)
Solutions
- Send JSON.stringify({provider: 'anthropic', method: 'token', token: tsk}) with Content-Type: application/json
- Use the %v offset — it points at the offending byte in the body
- If migrating from a form client, convert FormData to a plain object before stringify
Example fix
// before
body: new FormData(loginForm) // sends multipart, not JSON
// after
body: JSON.stringify({
provider: 'anthropic',
method: 'token',
token: tokenInput.value,
}) Defensive patterns
Strategy: validation
Validate before calling
function buildLoginPayload(provider: string, method: string, token: string): string {
for (const [k, v] of Object.entries({ provider, method, token })) {
if (typeof v !== 'string') throw new Error(`${k} must be a string`);
}
return JSON.stringify({ provider, method, token });
} Type guard
function isOAuthLoginBody(v: unknown): v is { provider: string; method: string; token: string } {
return typeof v === 'object' && v !== null
&& typeof (v as any).provider === 'string'
&& typeof (v as any).method === 'string'
&& typeof (v as any).token === 'string';
} Try / catch
try {
const res = await fetch('/api/oauth/login', {...});
if (res.status === 400 && (await res.text()).startsWith('invalid JSON')) {
/* %v points at the bad byte — JSON.parse locally to find and fix it */
}
} catch (e) { /* network */ } Prevention
- Never send FormData to this endpoint — convert to a plain object first
- Apply JSON.stringify exactly once
- All three fields are strings; method must be one of browser/device_code/token per provider
When it happens
Trigger: POST /api/oauth/login with unquoted keys, {"method": 5} (number vs string), double-encoded JSON, or a body like 'provider=openai' (form-encoded without JSON conversion).
Common situations: Sending FormData/x-www-form-urlencoded directly; JSON.stringify applied twice; hand-built curl with shell-mangled quotes; token containing an unescaped quote breaking a template-built string.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/75fecb5ea120b49d.
Report an issue: GitHub.