gastownhall/beads · error

opening log file: %w

Error message

opening log file: %w

What it means

Start() opens (creating if needed) the dolt server log file at logPath(beadsDir) with mode 0600 before launching the child process. If os.OpenFile fails, the error is wrapped as 'opening log file'. The server refuses to start without a log sink because diagnostics for later failures (e.g. GH#3290) depend on it.

Source

Thrown at internal/doltserver/doltserver.go:1347

		// only intervene between runs. See logrotate.go for the caveat discussion.
		maybeRotateLog(beadsDir)

		// Resolve .doltcfg once, before the port retry loop — it does not
		// depend on the port, and an ambiguous both-exist result must fail
		// Start() outright rather than retry into a fresh unintended
		// $data_dir/.doltcfg (see resolveCfgDir).
		var cfgDir string
		if useArchiveLevelConfig {
			cfgDir, err = resolveCfgDir(doltDir)
			if err != nil {
				return nil, fmt.Errorf("resolving .doltcfg directory: %w", err)
			}
		}

		// Open log file
		logFile, err := os.OpenFile(logPath(beadsDir), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600) //nolint:gosec // G304: logPath derives from user-configured beadsDir
		if err != nil {
			return nil, fmt.Errorf("opening log file: %w", err)
		}

		// Resolve the port to use. Explicit ports (env/config) go through
		// reclaimPort for conflict detection. Port 0 means ephemeral — allocate
		// a fresh port from the OS with retry for TOCTOU races.
		actualPort = cfg.Port
		explicitPort := actualPort > 0

		if explicitPort {
			// Explicit port: check for conflicts and adopt existing servers.
			adoptPID, reclaimErr := reclaimPort(cfg.Host, actualPort, beadsDir)
			if reclaimErr != nil {
				_ = logFile.Close()
				return nil, fmt.Errorf("cannot start dolt server on port %d: %w", actualPort, reclaimErr)
			}
			if adoptPID > 0 {
				_ = logFile.Close()
				_ = os.WriteFile(pidPath(beadsDir), []byte(strconv.Itoa(adoptPID)), 0600)

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check permissions/ownership of the beads data directory and fix with chown/chmod.
  2. Verify no file/directory with the log file's name blocks creation.
  3. Free disk space or inodes if the volume is full.
  4. Check ulimit -n (open file limit) if EMFILE is reported and close leaked descriptors.

Example fix

// before
ls -l ~/.beads/logs
// after
chmod u+w ~/.beads ~/.beads/logs
# or clear space: df -h ~/.beads
Defensive patterns

Strategy: validation

Validate before calling

const logFile = path.join(dataDir, 'dolt.log');
try { await fs.appendFile(logFile, ''); } // probes create+write
catch { throw new Error(`Cannot write dolt log at ${logFile}; fix permissions/disk`); }

Type guard

null

Try / catch

try {
  await bdStart();
} catch (e) {
  if (/opening log file/.test(e.message)) {
    // inspect errno: EACCES -> permissions, ENOSPC -> disk, EMFILE -> fd limit
  }
  throw e;
}

Prevention

When it happens

Trigger: os.OpenFile(logPath(beadsDir), O_CREATE|O_APPEND|O_WRONLY, 0600) fails: unwritable directory, path is a directory, or too many open files.

Common situations: Beads dir permissions wrong after a user/uid change; disk full; logPath colliding with a directory of the same name; EMFILE under heavy fd usage in long-lived processes.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/b752028699fe50c5. Report an issue: GitHub.