{"record":{"id":"f390756fc517fab3","repo":"charmbracelet/crush","slug":"create-config-directory-w","errorCode":null,"errorMessage":"create config directory: %w","messagePattern":"create config directory: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/config/store.go","lineNumber":280,"sourceCode":"\n// lockConfig acquires both the in-process mutex and a cross-process flock\n// on the config file for the given scope. Callers that need to do I/O\n// between reading and writing (e.g. an HTTP token exchange) must use\n// lockConfig explicitly rather than atomicWrite.\n//\n// The returned release function drops both locks. Callers must call it\n// as soon as the file access is complete — no I/O should be performed\n// while the lock is held.\nfunc (s *ConfigStore) lockConfig(scope Scope) (func(), error) {\n\ts.mu.Lock()\n\tpath, err := s.configPath(scope)\n\tif err != nil {\n\t\ts.mu.Unlock()\n\t\treturn nil, err\n\t}\n\tif err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {\n\t\ts.mu.Unlock()\n\t\treturn nil, fmt.Errorf(\"create config directory: %w\", err)\n\t}\n\tctx, cancel := context.WithTimeout(context.Background(), configLockDeadline)\n\tdefer cancel()\n\trelease, err := lock.File(ctx, path+\".lock\")\n\tif err != nil {\n\t\ts.mu.Unlock()\n\t\treturn nil, fmt.Errorf(\"acquire config lock: %w\", err)\n\t}\n\treturn func() {\n\t\trelease()\n\t\ts.mu.Unlock()\n\t}, nil\n}\n\n// atomicWrite handles the lock-read-transform-write-unlock cycle for\n// config file mutations. The fn callback receives the current file\n// contents (raw bytes, or {} if the file is missing) and must return the\n// new contents. fn must be pure — no I/O, no network calls.","sourceCodeStart":262,"sourceCodeEnd":298,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/config/store.go#L262-L298","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)"],"exampleFix":"// before\nos.Setenv(\"XDG_CONFIG_HOME\", \"/ro-mount/config\") // read-only volume\nstore.AtomicWrite(...)\n// after\nos.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(os.TempDir(), \"app-config\"))\nos.MkdirAll(os.Getenv(\"XDG_CONFIG_HOME\"), 0o755) // verify writable before use","handlingStrategy":"validation","validationCode":"dir := filepath.Dir(configPath)\nif info, err := os.Stat(dir); err != nil {\n    if err := os.MkdirAll(dir, 0o755); err != nil {\n        return fmt.Errorf(\"config dir %s not creatable: %w\", dir, err)\n    }\n} else if !info.IsDir() {\n    return fmt.Errorf(\"%s exists but is not a directory\", dir)\n} else if f, err := os.CreateTemp(dir, \".wtest\"); err != nil {\n    return fmt.Errorf(\"config dir %s not writable: %w\", dir, err)\n} else { f.Close(); os.Remove(f.Name()) }","typeGuard":"func configDirWritable(dir string) bool {\n    fi, err := os.Stat(dir)\n    return err == nil && fi.IsDir() && unix.Access(dir, unix.W_OK) == nil\n}","tryCatchPattern":"var lockErr *fs.PathError\nif errors.As(err, &lockErr) && errors.Is(lockErr, fs.ErrPermission) {\n    return fmt.Errorf(\"fix permissions on %s: %w\", filepath.Dir(configPath), err)\n}","preventionTips":["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"],"tags":["filesystem","permissions","config-directory"],"backgroundTag":"config-directory-unwritable","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}