gastownhall/beads · error

proxy.ForceStopUnverified: read %s: %w

Error message

proxy.ForceStopUnverified: read %s: %w

What it means

readForceStopRecord parses the PID/lock record file used by force-stop and wraps any read or parse failure in this error. It is thrown when the record exists but cannot be read/decoded, so ForceStopUnverified cannot determine the target PID and aborts instead of guessing.

Source

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

	if err := inspectAndStopUnverifiedPID(rootDir, record.Pid, deadline, report); err != nil {
		return err
	}
	return quarantineForceStopRecord(rootDir, pidName, record, report)
}

func readForceStopRecord(rootDir, pidName string, report *ForceStopReport) (*pidfile.PidFile, error) {
	record, err := pidfile.Read(rootDir, pidName)
	if err != nil {
		if isMalformedPIDFileError(err) {
			return nil, unverifiableProcessError(
				"force-stop",
				report.RecordPath,
				0,
				err,
				unverifiableProcessChecks{},
			)
		}
		return nil, fmt.Errorf("proxy.ForceStopUnverified: read %s: %w", report.RecordPath, err)
	}
	if record == nil {
		return nil, nil
	}
	report.RecordFound = true
	report.PID = record.Pid
	return record, nil
}

func requireUnverifiableRecord(rootDir string, record *pidfile.PidFile, wantKind string) error {
	if err := record.ValidateV2(wantKind); err != nil {
		return nil
	}
	rootID, err := identity.RootID(rootDir)
	if err != nil {
		// Failing open here would route a possibly-verifiable record into the
		// destructive force path; surface the identity failure instead.
		return fmt.Errorf("proxy.ForceStopUnverified: resolve workspace identity: %w", err)

View on GitHub (pinned to 71377f2769)

Solutions

  1. Inspect the record file at report.RecordPath; if it is empty or corrupt and no live process owns it, delete it and retry
  2. Check the wrapped cause (%w) for the specific decode/IO error
  3. Retry the force-stop after any concurrent bd process finishes writing the record
  4. Recreate a healthy state by running a normal bd command in the workspace, which rewrites the records

Example fix

// before
record, err := readForceStopRecord(rootDir, pidName, report)
// after
record, err := readForceStopRecord(rootDir, pidName, report)
if err != nil {
    if data, readErr := os.ReadFile(report.RecordPath); readErr == nil && len(bytes.TrimSpace(data)) == 0 {
        _ = os.Remove(report.RecordPath)
        record, err = readForceStopRecord(rootDir, pidName, report)
    }
}
Defensive patterns

Strategy: retry

Validate before calling

func recordReadable(path string) error {
    data, err := os.ReadFile(path)
    if err != nil {
        if os.IsNotExist(err) {
            return nil // no record is fine
        }
        return err
    }
    _, perr := strconv.Atoi(strings.TrimSpace(string(data)))
    return perr
}

Try / catch

report, err := proxy.ForceStopUnverified(rootDir)
if err != nil && strings.Contains(err.Error(), "read ") {
    // retry once; a concurrent writer may have been mid-write
    time.Sleep(500 * time.Millisecond)
    report, err = proxy.ForceStopUnverified(rootDir)
}

Prevention

When it happens

Trigger: Calling ForceStopUnverified when readForceStopRecord fails to read or decode <rootDir>/<pidName> (e.g. truncated PID file, non-numeric content, transient I/O error) — after the lock was determined to be held.

Common situations: A PID file partially written by a crashed process, manual edits to PID/lock files, or a concurrent writer racing with the force-stop read.

Related errors


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