amir20/dozzle · error

failed to create config file

Error message

failed to create config file: %w

What it means

The handler opens h.configPath with os.Create after ensuring ./data exists. If the file cannot be created or truncated (permissions, missing parent dir, path is a directory, disk full), the error is wrapped with this message and the config is not saved.

Solutions

  1. Check the wrapped syscall error (EACCES/EISDIR/ENOENT) for the cause
  2. Ensure the parent directory of configPath exists and is writable (mkdir -p on its dir)
  3. If configPath is a directory, change it to a file path
  4. Run the process with a user that has write access to the path, or mount a writable volume

Example fix

// before
--notification-config ./data/notifications/  # os.Create on a directory fails
// after
--notification-config ./data/notifications.yml
Defensive patterns

Strategy: try-catch

Validate before calling

dir := filepath.Dir(h.configPath)
if fi, err := os.Stat(dir); err != nil || !fi.IsDir() {
    os.MkdirAll(dir, 0755)
}

Try / catch

file, err := os.Create(h.configPath)
if err != nil {
    if errors.Is(err, fs.ErrPermission) || errors.Is(err, syscall.EISDIR) { /* fix path/permissions */ }
    return err
}

Prevention

When it happens

Trigger: os.Create(h.configPath) failing: no write permission in the target directory, configPath points at an existing directory, parent directory doesn't exist (MkdirAll only covers ./data, not configPath's dir), or filesystem full/read-only.

Common situations: configPath set to a subdirectory like ./data/notifications/ (a dir) instead of a file; running as an unprivileged user without rights to a custom --config path; custom configPath outside ./data whose parents were never created.

Understand the failure class

Background: "open() failed", "failed to open file", "cannot create file" — what a file open error means and how to fix it — this error's family across 42 libraries.

Related errors


AI-assisted analysis of amir20/dozzle@d9463cbe21 (2026-09-07). Data as JSON: /api/errors/734ef56e766adece. Report an issue: GitHub.

Appendix: source

Thrown at internal/support/cli/agent_command.go:63

func (h *persistingNotificationHandler) GetNotificationStats() []types.SubscriptionStats {
	return h.manager.GetNotificationStats()
}

func (h *persistingNotificationHandler) HandleNotificationConfig(subscriptions []types.SubscriptionConfig, dispatchers []types.DispatcherConfig) error {
	// Update the manager
	if err := h.manager.HandleNotificationConfig(subscriptions, dispatchers); err != nil {
		return err
	}

	// Save to disk
	if err := os.MkdirAll("./data", 0755); err != nil {
		return fmt.Errorf("failed to create data directory: %w", err)
	}

	file, err := os.Create(h.configPath)
	if err != nil {
		return fmt.Errorf("failed to create config file: %w", err)
	}
	defer file.Close()

	if err := h.manager.WriteConfig(file); err != nil {
		return fmt.Errorf("failed to save config: %w", err)
	}

	log.Debug().Str("path", h.configPath).Msg("Saved notification config to disk")
	return nil
}

// SetCloudStreamLogs applies the hub's log-streaming choice to this agent's own
// cloud client. The agent streams its logs to cloud itself rather than through
// the hub, so without this a user who turned streaming off saw it stop on the
// hub while every agent kept sending.
func (h *persistingNotificationHandler) SetCloudStreamLogs(enabled *bool) {
	cc := h.cloudConfig.Load()
	if cc == nil {

View on GitHub (pinned to d9463cbe21)