charmbracelet/crush · error

failed to delete config field %s: %w

Error message

failed to delete config field %s: %w

What it means

RemoveConfigField deletes a key via sjson.Delete inside atomicWrite. This error wraps an sjson.Delete failure — the current file content could not be parsed as JSON or the path could not be resolved for deletion.

Source

Thrown at internal/config/store.go:495

//
// Caller must hold writeMu.
func (s *ConfigStore) pinPreferredModelLocked(modelType SelectedModelType, model SelectedModel) {
	if s.overrides.Models == nil {
		s.overrides.Models = make(map[SelectedModelType]SelectedModel)
	}
	s.overrides.Models[modelType] = model
}

// RemoveConfigField removes a key from the config file for the given scope.
// After a successful write, it automatically reloads config to keep in-memory
// state fresh.
//
// The write is protected by an in-process mutex and a cross-process flock.
func (s *ConfigStore) RemoveConfigField(scope Scope, key string) error {
	err := s.atomicWrite(scope, func(data []byte) ([]byte, error) {
		v, sErr := sjson.Delete(string(data), key)
		if sErr != nil {
			return nil, fmt.Errorf("failed to delete config field %s: %w", key, sErr)
		}
		return []byte(v), nil
	})
	if err != nil {
		return err
	}

	if err := s.autoReload(context.Background()); err != nil {
		slog.Warn("Config file updated but failed to reload in-memory state", "error", err)
	}

	return nil
}

// UpdatePreferredModel updates the preferred model for the given type and
// persists it to the config file at the given scope. The selected model and
// the recent-models list are written together in a single config write.
//

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Validate the config JSON (jq . <configfile>) and repair syntax errors
  2. Confirm the target path exists and its parents are objects/arrays
  3. Use the exact key path that was used to set the field (same escaping)
  4. Back up and regenerate the config if it is corrupted

Example fix

// before
store.RemoveConfigField(scope, "providers."+providerID+".api_key") // providerID contains a dot
// after
if strings.Contains(providerID, ".") {
    return fmt.Errorf("provider ID %q cannot be addressed as a config path", providerID)
}
store.RemoveConfigField(scope, "providers."+providerID+".api_key")
Defensive patterns

Strategy: validation

Validate before calling

raw, _ := os.ReadFile(configPath)
if !json.Valid(raw) {
    return fmt.Errorf("config JSON invalid; cannot delete field %s", key)
}
if !gjson.GetBytes(raw, key).Exists() {
    return nil // nothing to delete; skip the write entirely
}

Type guard

func fieldExists(data []byte, path string) bool {
    return gjson.GetBytes(data, path).Exists()
}

Try / catch

if err := store.RemoveConfigField(scope, key); err != nil {
    if strings.Contains(err.Error(), "failed to delete config field") {
        return fmt.Errorf("cannot delete %s (check path parents are objects): %w", key, err)
    }
    return err
}

Prevention

When it happens

Trigger: Calling RemoveConfigField when the config file contains invalid JSON, or the key path traverses a non-object/non-array node (e.g. deleting providers.foo.bar when providers.foo is a string), or malformed path syntax.

Common situations: Corrupted or hand-edited config; deleting nested keys under a key that was overwritten with a scalar; keys containing dots interpreted as nested paths; race where another process wrote invalid JSON between read and delete (mitigated by flock but possible with external writers).

Related errors


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