gastownhall/beads · error

remove dead spawn marker: %w

Error message

remove dead spawn marker: %w

What it means

This error wraps a failure to remove a spawn marker file that belongs to a process already confirmed dead. The library throws it because a dead marker left on disk would keep triggering spurious spawn-owner checks; if removal fails for a reason other than the file not existing, the failure is surfaced instead of silently ignored.

Source

Thrown at internal/storage/dbproxy/proxy/endpoint.go:1044

}

func inspectSpawnMarkerLocked(rootDir string) (bool, error) {
	marker, err := readSpawnMarker(rootDir)
	if err != nil {
		return false, fmt.Errorf("inspect proxy spawn marker: %w", err)
	}
	if marker == nil {
		return false, nil
	}
	matched, err := procid.Verify(marker.PID, procid.Token(marker.Birth))
	if err != nil {
		return false, fmt.Errorf("verify proxy spawn owner pid %d: %w", marker.PID, err)
	}
	if matched {
		return true, nil
	}
	if err := os.Remove(filepath.Join(rootDir, spawnMarkerFileName)); err != nil && !errors.Is(err, fs.ErrNotExist) {
		return false, fmt.Errorf("remove dead spawn marker: %w", err)
	}
	return false, nil
}

func clearSpawnMarkerAfterLock(rootDir string) error {
	err := os.Remove(filepath.Join(rootDir, spawnMarkerFileName))
	if err != nil && !errors.Is(err, fs.ErrNotExist) {
		return err
	}
	return nil
}

func clearOwnSpawnMarker(rootDir string, own spawnMarker) error {
	deadline := time.Now().Add(shutdownConfirmDeadline)
	for {
		marker, err := readSpawnMarker(rootDir)
		if err != nil || marker == nil {
			return err

View on GitHub (pinned to 71377f2769)

Solutions

  1. Fix permissions on the database root directory so the current user can delete files in it (chown/chmod)
  2. Remove the marker file manually: rm <dbdir>/.bd-spawn-marker (actual spawnMarkerFileName)
  3. Check the wrapped cause (%w); if the marker path is a directory, remove the directory instead
  4. If the marker is already gone, the matching errors.Is(err, fs.ErrNotExist) branch means no action is needed — rerun the operation

Example fix

// before
if err := os.Remove(filepath.Join(rootDir, spawnMarkerFileName)); err != nil && !errors.Is(err, fs.ErrNotExist) {
    return false, fmt.Errorf("remove dead spawn marker: %w", err)
}
// after
markerPath := filepath.Join(rootDir, spawnMarkerFileName)
if err := os.Remove(markerPath); err != nil && !errors.Is(err, fs.ErrNotExist) {
    _ = os.Chmod(rootDir, 0o755)
    if err2 := os.Remove(markerPath); err2 != nil && !errors.Is(err2, fs.ErrNotExist) {
        return false, fmt.Errorf("remove dead spawn marker: %w", err2)
    }
}
Defensive patterns

Strategy: try-catch

Validate before calling

func canRemoveMarker(rootDir string) error {
    info, err := os.Stat(rootDir)
    if err != nil {
        return err
    }
    if !info.IsDir() {
        return fmt.Errorf("%s is not a directory", rootDir)
    }
    f, err := os.CreateTemp(rootDir, ".wtest")
    if err != nil {
        return err
    }
    _ = f.Close()
    return os.Remove(f.Name())
}

Try / catch

if err := verifySpawnOwner(rootDir); err != nil {
    if strings.Contains(err.Error(), "remove dead spawn marker") {
        fmt.Fprintf(os.Stderr, "fix permissions on %s, then retry: %v\n", rootDir, err)
    }
}

Prevention

When it happens

Trigger: After procid.Verify reports the recorded PID is not running, calling os.Remove on <rootDir>/<spawnMarkerFileName> fails with an error that is not fs.ErrNotExist — e.g. EACCES/EPERM on the directory or file, or the path is a directory rather than a file.

Common situations: Read-only or root-owned database directory after restoring a backup with wrong ownership, stale markers under a directory owned by a different user/container UID mismatch, or a directory named like the marker file blocking removal.

Related errors


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