charmbracelet/crush · error
failed to set config field %s: %w
Error message
failed to set config field %s: %w
What it means
writeConfigFields applies sjson.Set for each key inside the atomicWrite transform. This error wraps an sjson parse/mutation failure — sjson could not parse the current JSON or could not set the given path, so the write is aborted.
Source
Thrown at internal/config/store.go:406
// already published an updated clone and capture the snapshot themselves
// (update). Both of those run under writeMu, which is what keeps the
// snapshot map free of concurrent writers.
func (s *ConfigStore) writeConfigFields(scope Scope, kv map[string]any) error {
// Sort keys for deterministic output regardless of map iteration
// order. This also ensures consistent results when callers pass
// overlapping JSONPath keys (e.g. "a" and "a.b").
keys := make([]string, 0, len(kv))
for k := range kv {
keys = append(keys, k)
}
slices.Sort(keys)
return s.atomicWrite(scope, func(data []byte) ([]byte, error) {
v := string(data)
for _, key := range keys {
var sErr error
if v, sErr = sjson.Set(v, key, kv[key]); sErr != nil {
return nil, fmt.Errorf("failed to set config field %s: %w", key, sErr)
}
}
return []byte(v), nil
})
}
// mutateInMemory applies a copy-on-write change to the config without
// persisting. Under writeMu it clones the live config, lets mutate edit the
// clone, and publishes it. This is the single primitive every in-memory
// config change goes through, so a published Config is never mutated in
// place and readers always see a consistent snapshot.
func (s *ConfigStore) mutateInMemory(mutate func(*Config)) {
s.writeMu.Lock()
defer s.writeMu.Unlock()
nc := s.Config().cloneForWrite()
mutate(nc)
s.setConfig(nc)View on GitHub (pinned to 7944b8e522)
Solutions
- Validate the config file parses as JSON (jq . crush.json) and fix syntax errors
- Check that intermediate path segments hold objects, not scalars
- Escape or sanitize dots/special chars in dynamic keys (provider IDs) when building the sjson path
- Upgrade to a version with the fix if the path syntax is the culprit
Example fix
// before
s.SetConfigField(scope, "providers.my.org/api.api_key", k) // dots parsed as path segments
// after
path := fmt.Sprintf("providers.%s.api_key", sjsonEscapeKey(providerID)) // e.g. "my\\.org/api"
s.SetConfigField(scope, path, k) Defensive patterns
Strategy: validation
Validate before calling
raw, err := os.ReadFile(configPath)
if err == nil && len(raw) > 0 {
if !json.Valid(raw) {
return fmt.Errorf("config file contains invalid JSON; fix before writing")
}
}
// also verify the path segment parent is an object
var m map[string]any
json.Unmarshal(raw, &m); _, ok := m["providers"]; if !ok { return errors.New("no providers object") } Type guard
func canSetPath(data []byte, path string) bool {
return gjson.GetBytes(data, strings.Split(path, ".")[0]).Exists() || len(data) == 0
} Try / catch
if err := store.SetConfigField(scope, key, val); err != nil {
var handled bool
if strings.Contains(err.Error(), "failed to set config field") {
fmt.Fprintf(os.Stderr, "check JSON validity of config and path syntax for %s: %v\n", key, err)
handled = true
}
if !handled { return err }
} Prevention
- Never hand-edit config files without validating with jq afterward
- Escape dots in dynamic keys (provider IDs) when building sjson paths
- Ensure intermediate JSON nodes are objects before setting nested paths
- Use the same key-naming scheme for set and delete operations
When it happens
Trigger: Calling SetConfigField with a key/path that sjson cannot apply (e.g. setting a child under a scalar value like providers.foo.api_key.nested, or setting an array index on a non-array), or the existing config file contains invalid JSON.
Common situations: Hand-edited or corrupted crush.json with malformed JSON; a path collision where an intermediate key holds a string/number instead of an object; programmatic key built with a typo or special characters (dots in provider IDs) that sjson misinterprets as path segments.
Related errors
- failed to delete config field %s: %w
- failed to decode response: %w
- error parsing parameters: %s
- questions must be an array: %w
- failed to marshal GraphQL request: %w
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/157dcee1dc2c35ed.
Report an issue: GitHub.