larksuite/cli · error

busdiscover: write pid tmp: %w

Error message

busdiscover: write pid tmp: %w

What it means

WritePIDFile wraps the failure of vfs.WriteFile when writing the temporary pid file (bus.pid.tmp, mode 0600) inside the already-created eventsDir. On failure it releases the alive lock before returning so the lock is not leaked. The underlying OS error is preserved via %w.

Source

Thrown at internal/event/adapter/localbus/busdiscover/pidfile.go:52

	return h.lock.Unlock()
}

// WritePIDFile takes the alive lock and atomically writes pid + RFC3339 start time.
// Returns lockfile.ErrHeld if another bus holds the lock.
func WritePIDFile(eventsDir string, pid int) (*Handle, error) {
	if err := vfs.MkdirAll(eventsDir, 0700); err != nil {
		return nil, fmt.Errorf("busdiscover: mkdir %s: %w", eventsDir, err)
	}
	lock := lockfile.New(filepath.Join(eventsDir, aliveLockFileName))
	if err := lock.TryLock(); err != nil {
		return nil, err
	}
	pidPath := filepath.Join(eventsDir, pidFileName)
	tmpPath := pidPath + ".tmp"
	payload := fmt.Sprintf("%d\n%s\n", pid, time.Now().UTC().Format(time.RFC3339))
	if err := vfs.WriteFile(tmpPath, []byte(payload), 0600); err != nil {
		_ = lock.Unlock()
		return nil, fmt.Errorf("busdiscover: write pid tmp: %w", err)
	}
	if err := vfs.Rename(tmpPath, pidPath); err != nil {
		_ = vfs.Remove(tmpPath)
		_ = lock.Unlock()
		return nil, fmt.Errorf("busdiscover: rename pid file: %w", err)
	}
	return &Handle{lock: lock}, nil
}

func readPIDFile(eventsDir string) (int, time.Time, error) {
	pidPath := filepath.Join(eventsDir, pidFileName)
	data, err := vfs.ReadFile(pidPath)
	if err != nil {
		return 0, time.Time{}, err
	}
	lines := strings.SplitN(strings.TrimSpace(string(data)), "\n", 2)
	if len(lines) < 2 {
		return 0, time.Time{}, fmt.Errorf("busdiscover: malformed pid file %s", pidPath)

View on GitHub (pinned to 7fd6ef3c07)

Solutions

  1. Inspect the wrapped cause: ENOSPC/EDQUOT means free disk space or raise quota on the state volume
  2. Check the eventsDir still exists and is writable (ls -ld); recreate it if a cleaner removed it
  3. Retry WritePIDFile after transient conditions; the lock is released so reacquisition is safe
  4. Look for external cleanup jobs or security policies interfering with the config/state directory

Example fix

// before: writing to a volume that is full
err := busdiscover.WritePIDFile(eventsDir, pid) // fails: busdiscover: write pid tmp: ... no space left
// after: preflight the volume before starting the bus
if stat, err := os.Statvfs(eventsDir); err != nil || stat.Bavail == 0 { return fmt.Errorf("state volume full") }
err := busdiscover.WritePIDFile(eventsDir, pid)
Defensive patterns

Strategy: retry

Validate before calling

// Preflight: confirm eventsDir exists and accepts writes before WritePIDFile
if info, err := os.Stat(eventsDir); err != nil || !info.IsDir() {
    return fmt.Errorf("events dir %s missing", eventsDir)
}
probe := filepath.Join(eventsDir, ".write-probe")
if err := os.WriteFile(probe, []byte("ok"), 0600); err != nil {
    return fmt.Errorf("events dir %s not writable: %w", eventsDir, err)
}
os.Remove(probe)

Try / catch

var h *busdiscover.Handle
var err error
for i := 0; i < 3; i++ {
    h, err = busdiscover.WritePIDFile(eventsDir, pid)
    if err == nil || !errors.As(err, new(*fs.PathError)) {
        break
    }
    time.Sleep(200 * time.Millisecond) // transient ENOSPC/cleaner race
}

Prevention

When it happens

Trigger: Calling WritePIDFile after the eventsDir was created and the lock acquired, but the tmp write fails: disk full, quota exceeded, permission loss on the directory, or the directory removed concurrently by another process.

Common situations: Disk full on the state partition (ENOSPC), another process or cleaner deleting the events dir between mkdir and write, SELinux/AppArmor denying writes, or a read-only remount of the state volume.

Related errors


AI-assisted analysis of larksuite/cli@7fd6ef3c07 (2026-09-04). Data as JSON: /api/errors/f4e93d49624a0d36. Report an issue: GitHub.