{"record":{"id":"5bc4ce8c71f1fa34","repo":"gastownhall/beads","slug":"proxy-forcestopunverified-record-s-changed-after","errorCode":null,"errorMessage":"proxy.ForceStopUnverified: record %s changed after pid %d was stopped; refusing to quarantine the replacement","messagePattern":"proxy\\.ForceStopUnverified: record (.+?) changed after pid (.+?) was stopped; refusing to quarantine the replacement","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/dbproxy/proxy/force_stop.go","lineNumber":317,"sourceCode":"\t\ttime.Sleep(shutdownConfirmPoll)\n\t}\n}\n\nfunc quarantineForceStopRecord(\n\trootDir string,\n\tpidName string,\n\trecord *pidfile.PidFile,\n\treport *ForceStopReport,\n) error {\n\tcurrent, err := pidfile.Read(rootDir, pidName)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"proxy.ForceStopUnverified: re-read %s: %w\", report.RecordPath, err)\n\t}\n\tif current == nil {\n\t\treturn nil\n\t}\n\tif *current != *record {\n\t\treturn fmt.Errorf(\n\t\t\t\"proxy.ForceStopUnverified: record %s changed after pid %d was stopped; refusing to quarantine the replacement\",\n\t\t\treport.RecordPath,\n\t\t\trecord.Pid,\n\t\t)\n\t}\n\ttarget, err := quarantineRecord(rootDir, pidName, time.Now())\n\tif err != nil {\n\t\treturn fmt.Errorf(\"proxy.ForceStopUnverified: quarantine %s: %w\", report.RecordPath, err)\n\t}\n\treport.QuarantinedPath = target\n\treturn nil\n}\n","sourceCodeStart":299,"sourceCodeEnd":330,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/dbproxy/proxy/force_stop.go#L299-L330","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Re-run the force-stop so it re-reads the current record and targets the correct pid","Check who/what modified the record (concurrent bd process, restart supervisor) and serialize force-stops","If the replacement record is genuinely unwanted, delete it explicitly rather than forcing quarantine"],"exampleFix":"// before: racing force-stop\nforceStopRecord(rootDir, staleRecord)\n// after: re-read then stop under lock\nrec, err := readPidRecord(rootDir)\nif err != nil { return err }\nforceStopRecordLocked(rootDir, rec)","handlingStrategy":"validation","validationCode":"rec, err := readPidRecord(rootDir)\nif err != nil { return err }\n// re-read right before force-stop to confirm unchanged\nagain, err := readPidRecord(rootDir)\nif err != nil || *again != *rec { return fmt.Errorf(\"record changed; retry\") }","typeGuard":"func recordStale(a, b *PidRecord) bool { return a == nil || b == nil || *a != *b }","tryCatchPattern":"if err := forceStopRecord(rootDir, rec); err != nil {\n  if strings.Contains(err.Error(), \"changed after pid\") { /* retry with fresh read */ }\n  return err\n}","preventionTips":["Serialize force-stop operations via a single control path","Re-read the pid record immediately before stopping","Check for restart supervisors before force-stopping"],"tags":["race-condition","quarantine","process-lifecycle"],"backgroundTag":"record-changed-after-stop","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}