gastownhall/beads · error
proxy.ForceStopUnverified: timeout must be positive, got %s
Error message
proxy.ForceStopUnverified: timeout must be positive, got %s
What it means
ForceStopUnverified validates its optional Timeout option and rejects non-positive values because a non-positive timeout makes the force-stop wait loop meaningless. The library throws this synchronously at the start of the call before any destructive action is taken.
Source
Thrown at internal/storage/dbproxy/proxy/force_stop.go:65
// 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,
server.PIDFileName,
pidfile.KindDoltBackend,
timeout,
&backendReport,
)
if backendReport.RecordFound || backendErr != nil {
report.Backend = &backendReportView on GitHub (pinned to 71377f2769)
Solutions
- Pass a positive timeout, e.g. proxy.WithTimeout(30*time.Second)
- Omit the timeout option entirely to use the built-in shutdownConfirmDeadline default
- Fix the caller so a zero/negative configured timeout falls back to a positive default before calling ForceStopUnverified
Example fix
// before
report, err := proxy.ForceStopUnverified(rootDir, proxy.WithTimeout(cfg.StopTimeout)) // cfg.StopTimeout == 0
// after
timeout := cfg.StopTimeout
if timeout <= 0 {
timeout = 30 * time.Second
}
report, err := proxy.ForceStopUnverified(rootDir, proxy.WithTimeout(timeout)) Defensive patterns
Strategy: validation
Validate before calling
func safeTimeout(d time.Duration) time.Duration {
if d <= 0 {
return 30 * time.Second
}
return d
}
// usage: proxy.WithTimeout(safeTimeout(cfg.StopTimeout)) Type guard
func validTimeout(d time.Duration) bool { return d > 0 } Try / catch
report, err := proxy.ForceStopUnverified(rootDir, opts...)
if err != nil && strings.Contains(err.Error(), "timeout must be positive") {
report, err = proxy.ForceStopUnverified(rootDir) // use library default
} Prevention
- Clamp configured timeouts to positive defaults before passing options
- Never pass time.Duration(0) expecting 'infinite'; omit the option instead
- Sanitize config values loaded from env/files
- Add unit tests for zero/negative timeout configs
When it happens
Trigger: Calling proxy.ForceStopUnverified(rootDir, proxy.WithTimeout(0)) or WithTimeout(negative duration); note that a single option with Timeout==0 is treated as 'not set' and defaults to shutdownConfirmDeadline, but any explicitly non-positive timeout other than that zero-in-one-option case triggers this error (e.g. WithTimeout(-time.Second)).
Common situations: Test code or callers computing a timeout from a config value that is unset/zero/negative, passing time.Duration(0) thinking it means 'infinite' or 'default'.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- proxy.ForceStopUnverified: at most one options value is allo
- issue must not be nil
- add dep: dep must not be nil
- add dep: IssueID and DependsOnID must be non-empty
- add label: id must not be empty
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/4814086917ab86aa.
Report an issue: GitHub.