gastownhall/beads · error

proxy.ForceStopUnverified: record %s changed after pid %d wa

Error message

proxy.ForceStopUnverified: record %s changed after pid %d was stopped; refusing to quarantine the replacement

What it means

Thrown by quarantineForceStopRecord in internal/storage/dbproxy/proxy/force_stop.go when, after a force-stop killed the pid named in a record, a re-read of the record on disk differs from the in-memory snapshot. This means another process replaced or modified the record after the pid died, so quarantining it would archive the wrong (replacement) data. The library refuses to move it to quarantine to avoid destroying a legitimate new record.

Source

Thrown at internal/storage/dbproxy/proxy/force_stop.go:317

		time.Sleep(shutdownConfirmPoll)
	}
}

func quarantineForceStopRecord(
	rootDir string,
	pidName string,
	record *pidfile.PidFile,
	report *ForceStopReport,
) error {
	current, err := pidfile.Read(rootDir, pidName)
	if err != nil {
		return fmt.Errorf("proxy.ForceStopUnverified: re-read %s: %w", report.RecordPath, err)
	}
	if current == nil {
		return nil
	}
	if *current != *record {
		return fmt.Errorf(
			"proxy.ForceStopUnverified: record %s changed after pid %d was stopped; refusing to quarantine the replacement",
			report.RecordPath,
			record.Pid,
		)
	}
	target, err := quarantineRecord(rootDir, pidName, time.Now())
	if err != nil {
		return fmt.Errorf("proxy.ForceStopUnverified: quarantine %s: %w", report.RecordPath, err)
	}
	report.QuarantinedPath = target
	return nil
}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Re-run the force-stop so it re-reads the current record and targets the correct pid
  2. Check who/what modified the record (concurrent bd process, restart supervisor) and serialize force-stops
  3. If the replacement record is genuinely unwanted, delete it explicitly rather than forcing quarantine

Example fix

// before: racing force-stop
forceStopRecord(rootDir, staleRecord)
// after: re-read then stop under lock
rec, err := readPidRecord(rootDir)
if err != nil { return err }
forceStopRecordLocked(rootDir, rec)
Defensive patterns

Strategy: validation

Validate before calling

rec, err := readPidRecord(rootDir)
if err != nil { return err }
// re-read right before force-stop to confirm unchanged
again, err := readPidRecord(rootDir)
if err != nil || *again != *rec { return fmt.Errorf("record changed; retry") }

Type guard

func recordStale(a, b *PidRecord) bool { return a == nil || b == nil || *a != *b }

Try / catch

if err := forceStopRecord(rootDir, rec); err != nil {
  if strings.Contains(err.Error(), "changed after pid") { /* retry with fresh read */ }
  return err
}

Prevention

When it happens

Trigger: Calling ForceStopUnverified (via forceStopRecord / forceStopRecordLocked) when a concurrent writer rewrites the pid record between the initial read/stop and the post-stop re-read; *current != *record.

Common situations: Two operators or automation scripts run force-stop concurrently; a supervisor restarts the daemon and the new process rewrites its own record while an old force-stop is in flight; stale tooling operating on a recycled pid.

Related errors


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