gastownhall/beads · error

proxy.ForceStopUnverified: at most one options value is…

Error message

proxy.ForceStopUnverified: at most one options value is allowed

What it means

ForceStopUnverified accepts variadic ForceStopOptions but is defined to take at most one options value. Passing more than one is a programming error and is rejected up front before any destructive work. This keeps the API contract unambiguous about timeout configuration.

Solutions

  1. Pass at most one ForceStopOptions value
  2. Merge desired fields into a single ForceStopOptions struct
  3. Call with no options to use the default shutdownConfirmDeadline

Example fix

// before
report, err := proxy.ForceStopUnverified(root, proxy.ForceStopOptions{Timeout: 5 * time.Second}, proxy.ForceStopOptions{})
// after
report, err := proxy.ForceStopUnverified(root, proxy.ForceStopOptions{Timeout: 5 * time.Second})
Defensive patterns

Strategy: validation

Validate before calling

func safeForceStop(root string, opts ...proxy.ForceStopOptions) (proxy.ForceStopReport, error) {
    if len(opts) > 1 {
        opts = opts[:1]
    }
    return proxy.ForceStopUnverified(root, opts...)
}

Prevention

When it happens

Trigger: Calling proxy.ForceStopUnverified(root, opt1, opt2) with two or more ForceStopOptions values.

Common situations: Copy-paste refactors that append an extra options value; wrapping helpers that spread plus pass an explicit option.

Related errors


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

Appendix: source

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

// unverifiable proxy or backend record. It applies the same procedure to the
// proxy record (proxy.pid) and the backend record (proxy-child.pid): a
// pre-v2 managed-local deployment leaves BOTH as legacy records, so covering
// only the proxy record would lock the advertised recovery out of the real
// upgrade topology.
//
// For each record: when its lock is free, the function holds it while
// inspecting, signaling, and quarantining the unchanged record. When the lock
// is held (the usual pre-upgrade-proxy case), it first inspects and signals
// the live PID, waits for the lock to become free, then quarantines only if
// the record is unchanged. Both flows accept an already-gone recorded
// process. An unverified live PID is never signaled unless its executable
// basename is exactly bd or dolt (with an optional .exe suffix) AND its
// command line scopes it to this workspace; where the platform cannot
// establish that scope, force-stop refuses rather than guessing.
func ForceStopUnverified(rootDir string, opts ...ForceStopOptions) (ForceStopReport, error) {
	report := ForceStopReport{RecordPath: pidfile.Path(rootDir, PIDFileName)}
	if len(opts) > 1 {
		return report, errors.New("proxy.ForceStopUnverified: at most one options value is allowed")
	}
	timeout := shutdownConfirmDeadline
	if len(opts) == 1 && opts[0].Timeout != 0 {
		timeout = opts[0].Timeout
	}
	if timeout <= 0 {
		return report, fmt.Errorf("proxy.ForceStopUnverified: timeout must be positive, got %s", timeout)
	}
	if err := advanceStopEpoch(rootDir); err != nil {
		return report, fmt.Errorf("proxy.ForceStopUnverified: publish stop epoch: %w", err)
	}

	proxyErr := forceStopRecord(rootDir, LockFileName, PIDFileName, pidfile.KindProxy, timeout, &report)

	backendReport := ForceStopReport{RecordPath: pidfile.Path(rootDir, server.PIDFileName)}
	backendErr := forceStopRecord(
		rootDir,
		server.LockFileName,

View on GitHub (pinned to 71377f2769)