charmbracelet/crush · error

failed to update preferred small model: %w

Error message

failed to update preferred small model: %w

What it means

Same as the large-model case: when the preferred small model needed a fallback correction, Load() persists it to the global config and returns this wrapped error if the write fails.

Source

Thrown at internal/config/load.go:161

	if err != nil {
		return nil, fmt.Errorf("failed to configure selected models: %w", err)
	}
	cfg.Models[SelectedModelTypeLarge] = resolved.Large
	cfg.Models[SelectedModelTypeSmall] = resolved.Small

	// Persist any fallback corrections while we still hold writeMu.
	if resolved.LargeFallback {
		if err := store.updateLocked(ScopeGlobal, func(c *Config) map[string]any {
			return store.updatePreferredModelFields(c, SelectedModelTypeLarge, resolved.Large)
		}); err != nil {
			return nil, fmt.Errorf("failed to update preferred large model: %w", err)
		}
	}
	if resolved.SmallFallback {
		if err := store.updateLocked(ScopeGlobal, func(c *Config) map[string]any {
			return store.updatePreferredModelFields(c, SelectedModelTypeSmall, resolved.Small)
		}); err != nil {
			return nil, fmt.Errorf("failed to update preferred small model: %w", err)
		}
	}
	store.SetupAgents()

	// Capture initial staleness snapshot
	// Capture initial staleness snapshot. Track every discovered config path,
	// not just the ones that loaded, so a config file created after startup
	// (e.g. a crushrc added mid-session) is detected as a change.
	store.captureStalenessSnapshot(append(slices.Clone(configPaths), loadedPaths...))

	return store, nil
}

// mustMarshalConfig marshals the config to JSON bytes, returning empty JSON on
// error.
func mustMarshalConfig(cfg *Config) []byte {
	data, err := json.Marshal(cfg)
	if err != nil {

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Make the global config file writable (chmod/chown)
  2. Validate and repair the global config JSON
  3. Check disk space
  4. Explicitly set a valid small preferred model in config so no fallback correction is needed

Example fix

// before
"options": {"preferred_model": {"small": "gpt-3.5-turbo-0301"}}  // stale, triggers fallback write
// after
"options": {"preferred_model": {"small": "openai/gpt-4o-mini"}}  // valid, no fallback write needed
Defensive patterns

Strategy: try-catch

Validate before calling

fi, err := os.Stat(globalConfigPath)
if err == nil && fi.Mode().Perm()&0o200 == 0 {
    return fmt.Errorf("global config %s is not writable", globalConfigPath)
}

Try / catch

store, err := config.Load(ctx, opts)
if err != nil {
    if strings.Contains(err.Error(), "failed to update preferred small model:") {
        return fmt.Errorf("global config unwritable or corrupt: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: resolved.SmallFallback is true and store.updateLocked(ScopeGlobal, updatePreferredModelFields...) errors while persisting the corrected small model selection.

Common situations: Read-only or unwritable global config; corrupted global crush.json; disk-full; containerized environments mounting the config dir read-only.

Related errors


AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29). Data as JSON: /api/errors/664d31665f68762c. Report an issue: GitHub.