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

  1. Check the backend log for the 'Malformed config file' entry which names the offending byte/position in config.json
  2. Validate the file: jq . "$(config path)" or a JSON linter, and fix the reported syntax error
  3. Fix permissions: chmod 600 config.json and chown it to the user running the backend process
  4. 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

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


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