sipeed/picoclaw · error
Failed to load config: %v
Error message
Failed to load config: %v
What it means
HTTP 500 returned by GET /api/models (handleListModels) when config.LoadConfig(h.configPath) fails. A missing config file is tolerated (defaults are returned), so this only fires when the file exists but cannot be read or parsed: permission denied, path is a directory, malformed JSON, or a failed legacy v0-to-v1 migration. The backend logs a 'Malformed config file' diagnostic with byte offset from wrapJSONError before responding.
Source
Thrown at web/backend/api/models.go:244
return false
}
changed := false
for _, model := range cfg.ModelList {
if normalizeStoredModelConfig(model) {
changed = true
}
}
return changed
}
// handleListModels returns all model_list entries with masked API keys.
//
// GET /api/models
func (h *Handler) handleListModels(w http.ResponseWriter, r *http.Request) {
cfg, err := config.LoadConfig(h.configPath)
if err != nil {
http.Error(w, fmt.Sprintf("Failed to load config: %v", err), http.StatusInternalServerError)
return
}
// Normalize legacy provider/model storage in memory so GET can round-trip
// through the current API shape without mutating the on-disk config.
normalizeStoredModelProviders(cfg)
defaultModel := cfg.Agents.Defaults.GetModelName()
modelStatuses := make([]modelConfigurationSummary, len(cfg.ModelList))
var wg sync.WaitGroup
wg.Add(len(cfg.ModelList))
for i, m := range cfg.ModelList {
go func(i int, m *config.ModelConfig) {
defer wg.Done()
modelStatuses[i] = modelConfigurationStatus(m)
}(i, m)
}View on GitHub (pinned to 49183d7e8d)
Solutions
- Check the backend log for the 'Malformed config file' entry which names the offending byte/position in config.json
- Validate the file: jq . "$(config path)" or a JSON linter, and fix the reported syntax error
- Fix permissions: chmod 600 config.json and chown it to the user running the backend process
- If migration fails (V0→V1 error in log), back up config.json, delete it, and let the app regenerate defaults, then re-add models
Example fix
# before: config.json left broken after hand edit
{
"model_list": [ {"model_name": "gpt",}, ],
}
# after: valid JSON, no trailing commas
{
"model_list": [
{ "model_name": "gpt", "model": "openai/gpt-4o", "provider": "openai" }
]
} Defensive patterns
Strategy: try-catch
Validate before calling
// local pre-flight when you control the host: confirm the config parses before the request // jq -e . "$CONFIG_PATH" >/dev/null || echo 'config.json is invalid — fix before using the API'
Try / catch
const res = await fetch('/api/models');
if (!res.ok) {
const detail = await res.text(); // contains the LoadConfig cause
throw new Error(`GET /api/models failed (${res.status}): ${detail}`);
}
const data = await res.json(); Prevention
- Never hand-edit config.json while the backend is running; use the API endpoints
- Validate config.json with a JSON linter after any manual change
- Keep the backend process and config file under the same OS user
- Back up config.json before app upgrades that change schema versions
When it happens
Trigger: GET /api/models when config.json on the server has a JSON syntax error (trailing comma, unescaped quote), is chmod 000 or owned by root while the backend runs as a normal user, has been replaced by a directory, or contains a legacy v0 payload that fails validateLegacyConfigDiagnostics/migrateV0ToV1.
Common situations: Hand-editing config.json and introducing a typo; running the service under a different user than the one that created the config; an app upgrade that changes schema version while the old file cannot migrate; another tool (sync agent, editor backup swap) corrupting or truncating the file mid-write.
Related errors
- Failed to save config: %v
- Failed to save config
- create request: %w
- failed to load config: %w
- build request: %w
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/dfb0b64463178e0b.
Report an issue: GitHub.