charmbracelet/crush · error
create config directory: %w
Error message
create config directory: %w
What it means
lockConfig acquires the in-process mutex and a cross-process flock for a config write. Before locking the file it ensures the config directory exists via os.MkdirAll. This error wraps an MkdirAll failure — the OS refused to create the directory that holds the config file.
Source
Thrown at internal/config/store.go:280
// lockConfig acquires both the in-process mutex and a cross-process flock
// on the config file for the given scope. Callers that need to do I/O
// between reading and writing (e.g. an HTTP token exchange) must use
// lockConfig explicitly rather than atomicWrite.
//
// The returned release function drops both locks. Callers must call it
// as soon as the file access is complete — no I/O should be performed
// while the lock is held.
func (s *ConfigStore) lockConfig(scope Scope) (func(), error) {
s.mu.Lock()
path, err := s.configPath(scope)
if err != nil {
s.mu.Unlock()
return nil, err
}
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
s.mu.Unlock()
return nil, fmt.Errorf("create config directory: %w", err)
}
ctx, cancel := context.WithTimeout(context.Background(), configLockDeadline)
defer cancel()
release, err := lock.File(ctx, path+".lock")
if err != nil {
s.mu.Unlock()
return nil, fmt.Errorf("acquire config lock: %w", err)
}
return func() {
release()
s.mu.Unlock()
}, nil
}
// atomicWrite handles the lock-read-transform-write-unlock cycle for
// config file mutations. The fn callback receives the current file
// contents (raw bytes, or {} if the file is missing) and must return the
// new contents. fn must be pure — no I/O, no network calls.View on GitHub (pinned to 7944b8e522)
Solutions
- Check permissions on the config path's parent directories and grant write access
- Verify no regular file exists where a directory component is expected; remove/rename it
- Confirm XDG_CONFIG_HOME/HOME point to a writable location
- Check the filesystem is not read-only or out of space (df, mount)
Example fix
// before
os.Setenv("XDG_CONFIG_HOME", "/ro-mount/config") // read-only volume
store.AtomicWrite(...)
// after
os.Setenv("XDG_CONFIG_HOME", filepath.Join(os.TempDir(), "app-config"))
os.MkdirAll(os.Getenv("XDG_CONFIG_HOME"), 0o755) // verify writable before use Defensive patterns
Strategy: validation
Validate before calling
dir := filepath.Dir(configPath)
if info, err := os.Stat(dir); err != nil {
if err := os.MkdirAll(dir, 0o755); err != nil {
return fmt.Errorf("config dir %s not creatable: %w", dir, err)
}
} else if !info.IsDir() {
return fmt.Errorf("%s exists but is not a directory", dir)
} else if f, err := os.CreateTemp(dir, ".wtest"); err != nil {
return fmt.Errorf("config dir %s not writable: %w", dir, err)
} else { f.Close(); os.Remove(f.Name()) } Type guard
func configDirWritable(dir string) bool {
fi, err := os.Stat(dir)
return err == nil && fi.IsDir() && unix.Access(dir, unix.W_OK) == nil
} Try / catch
var lockErr *fs.PathError
if errors.As(err, &lockErr) && errors.Is(lockErr, fs.ErrPermission) {
return fmt.Errorf("fix permissions on %s: %w", filepath.Dir(configPath), err)
} Prevention
- Point XDG_CONFIG_HOME/HOME at writable locations in sandboxes and containers
- Never place regular files where a config directory component is expected
- Check filesystem mount flags (read-only) before running config-mutating commands
- Run under the same user that owns the config directory
When it happens
Trigger: Calling any config-mutating API (atomicWrite -> lockConfig) when the config directory's parent path is missing and cannot be created: permission denied on the parent, a non-directory file exists at a path component, read-only filesystem, or disk full.
Common situations: XDG_CONFIG_HOME or config path pointed at a read-only mount or another user's directory; a regular file occupies ~/.config or the app dir path; running in a sandboxed/containerized env without write access to the home directory.
Related errors
- failed to create parent directories: %w
- failed to create output file: %w
- failed to access file: %w
- failed to create parent directories: %w
- session ID is required for accessing directories outside wor
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/f390756fc517fab3.
Report an issue: GitHub.