gastownhall/beads · error

open proxy log %q: %w

Error message

open proxy log %q: %w

What it means

ListenAndServe opens rootDir/<LogFileName> in append mode for proxy logging; failure is wrapped as 'open proxy log %q: %w'. Without the log file the proxy refuses to start, since operational logging is mandatory for the doomed-start/stop coordination protocol.

Source

Thrown at internal/storage/dbproxy/proxy/server.go:148

		return fmt.Errorf("clear proxy spawn marker: %w", err)
	}

	// Fast-abort: a concurrent `bd dolt stop` advances the stop epoch before
	// waiting (briefly) for proxy.lock, so an epoch that moved between the
	// spawning parent's read and this child taking the lock dooms this start.
	// Abort before opening any listener or booting the backend: the stopper
	// then observes a free lock within milliseconds instead of after a full
	// doomed boot-and-teardown cycle.
	if changed, err := stopEpochChanged(p.rootDir, p.stopEpoch); err != nil {
		return fmt.Errorf("check proxy stop epoch after acquiring %s: %w", LockFileName, err)
	} else if changed {
		return fmt.Errorf("%w for %s: stop epoch advanced before startup", errStartInterrupted, p.rootDir)
	}

	logPath := filepath.Join(p.rootDir, LogFileName)
	f, err := os.OpenFile(logPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o600) // #nosec G304 -- logPath is derived from operator-supplied config, not untrusted request input
	if err != nil {
		return fmt.Errorf("open proxy log %q: %w", logPath, err)
	}
	p.logger = log.New(f, "[proxy] ", log.LstdFlags|log.Lmicroseconds)
	defer func() { _ = f.Close() }()

	ctx, cancel := context.WithCancel(parentCtx)
	defer cancel()

	// Install signal handlers BEFORE Listen. Without this, Go's default
	// SIGTERM action terminates the process during the startup window
	// (Listen, pidfile write, backend Start, readiness wait), bypassing all
	// deferred cleanup including RemoveDatabaseProxyPidFile.
	sigCh := make(chan os.Signal, 1)
	signal.Notify(sigCh, syscall.SIGTERM, syscall.SIGINT)
	defer signal.Stop(sigCh)

	var sigReceived atomic.Bool
	go func() {
		select {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check the wrapped cause and fix permissions on the log file / rootDir
  2. Free disk space if the filesystem is full
  3. Remove or chown a stale log file owned by a different user

Example fix

// before
sudo chown otheruser:otheruser /data/proxy.log
// after
sudo chown $(whoami) /data/proxy.log && bd serve --root /data
Defensive patterns

Strategy: validation

Validate before calling

logPath := filepath.Join(rootDir, LogFileName)
if f, err := os.OpenFile(logPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o600); err != nil {
  return fmt.Errorf("pre-check log open: %w", err)
} else { f.Close() }

Try / catch

if err := p.ListenAndServe(ctx); err != nil {
  if strings.Contains(err.Error(), "open proxy log") { /* fix perms/disk, retry */ }
  return err
}

Prevention

When it happens

Trigger: Calling ListenAndServe when os.OpenFile(logPath, O_CREATE|O_APPEND|O_WRONLY, 0600) fails — bad path, permissions, disk full, or logPath pointing at a directory.

Common situations: Read-only or full filesystem; rootDir permission changed after install; overly restrictive umask or SELinux/AppArmor policy; log file owned by another user.

Related errors


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