charmbracelet/bubbletea · error

error opening file for logging: %w

Error message

error opening file for logging: %w

What it means

Returned by tea.LogToFile / tea.LogToFileWith when the debug log file cannot be opened with O_WRONLY|O_CREATE|O_APPEND and mode 0600. Because a TUI occupies the terminal, file logging is the standard way to debug Bubble Tea programs; this error means that setup failed before any logging was configured. It wraps the os.OpenFile error verbatim.

Source

Thrown at logging.go:38

//			os.Exit(1)
//	  }
//	  defer f.Close()
func LogToFile(path string, prefix string) (*os.File, error) {
	return LogToFileWith(path, prefix, log.Default())
}

// LogOptionsSetter is an interface implemented by stdlib's log and charm's log
// libraries.
type LogOptionsSetter interface {
	SetOutput(io.Writer)
	SetPrefix(string)
}

// LogToFileWith does allows to call LogToFile with a custom LogOptionsSetter.
func LogToFileWith(path string, prefix string, log LogOptionsSetter) (*os.File, error) {
	f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o600) //nolint:mnd
	if err != nil {
		return nil, fmt.Errorf("error opening file for logging: %w", err)
	}
	log.SetOutput(f)

	// Add a space after the prefix if a prefix is being specified and it
	// doesn't already have a trailing space.
	if len(prefix) > 0 {
		finalChar := prefix[len(prefix)-1]
		if !unicode.IsSpace(rune(finalChar)) {
			prefix += " "
		}
	}
	log.SetPrefix(prefix)

	return f, nil
}

View on GitHub (pinned to 351d2159f8)

Solutions

  1. Create the parent directory first (os.MkdirAll) before calling LogToFile
  2. Use a path you know is writable: os.TempDir(), the current dir, or XDG data/cache dirs
  3. Check directory permissions / run with appropriate privileges if the path must be system-wide
  4. Use tea.WithLogPath or set p.logger another way for non-file logging

Example fix

// before
f, err := tea.LogToFile("/var/log/myapp/debug.log", "debug")

// after
dir := filepath.Join(os.TempDir(), "myapp")
_ = os.MkdirAll(dir, 0o755)
f, err := tea.LogToFile(filepath.Join(dir, "debug.log"), "debug")
Defensive patterns

Strategy: validation

Validate before calling

func openDebugLog(dir, name string) (*os.File, error) {
    if err := os.MkdirAll(dir, 0o755); err != nil {
        return nil, err
    }
    // probe writability before handing path to bubbletea
    probe, err := os.OpenFile(filepath.Join(dir, name), os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o600)
    if err != nil {
        return nil, fmt.Errorf("log dir not writable: %w", err)
    }
    return probe, nil
}

Try / catch

f, err := tea.LogToFile(path, "debug")
if err != nil {
    // degrade: skip file logging rather than dying
    log.Printf("warning: debug logging disabled: %v", err)
    f = nil
}
defer func() { if f != nil { f.Close() } }()

Prevention

When it happens

Trigger: Path points to a nonexistent directory (ENOENT); permission denied (EACCES) for the file or its directory; path is a directory itself; read-only filesystem; path too long; too many open files (EMFILE).

Common situations: Log path like "/tmp/tea/debug.log" where /tmp/tea was never created; running as a user without write access to the chosen dir (e.g. /var/log); container images with read-only root filesystems; hard-coded absolute debug paths from README examples that don't exist on the machine.

Related errors


AI-assisted analysis of charmbracelet/bubbletea@351d2159f8 (2026-08-15). Data as JSON: /api/errors/6d02dd2e92f3ff00. Report an issue: GitHub.