{"record":{"id":"4814086917ab86aa","repo":"gastownhall/beads","slug":"proxy-forcestopunverified-timeout-must-be-positiv","errorCode":null,"errorMessage":"proxy.ForceStopUnverified: timeout must be positive, got %s","messagePattern":"proxy\\.ForceStopUnverified: timeout must be positive, got (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/dbproxy/proxy/force_stop.go","lineNumber":65,"sourceCode":"// inspecting, signaling, and quarantining the unchanged record. When the lock\n// is held (the usual pre-upgrade-proxy case), it first inspects and signals\n// the live PID, waits for the lock to become free, then quarantines only if\n// the record is unchanged. Both flows accept an already-gone recorded\n// process. An unverified live PID is never signaled unless its executable\n// basename is exactly bd or dolt (with an optional .exe suffix) AND its\n// command line scopes it to this workspace; where the platform cannot\n// establish that scope, force-stop refuses rather than guessing.\nfunc ForceStopUnverified(rootDir string, opts ...ForceStopOptions) (ForceStopReport, error) {\n\treport := ForceStopReport{RecordPath: pidfile.Path(rootDir, PIDFileName)}\n\tif len(opts) > 1 {\n\t\treturn report, errors.New(\"proxy.ForceStopUnverified: at most one options value is allowed\")\n\t}\n\ttimeout := shutdownConfirmDeadline\n\tif len(opts) == 1 && opts[0].Timeout != 0 {\n\t\ttimeout = opts[0].Timeout\n\t}\n\tif timeout <= 0 {\n\t\treturn report, fmt.Errorf(\"proxy.ForceStopUnverified: timeout must be positive, got %s\", timeout)\n\t}\n\tif err := advanceStopEpoch(rootDir); err != nil {\n\t\treturn report, fmt.Errorf(\"proxy.ForceStopUnverified: publish stop epoch: %w\", err)\n\t}\n\n\tproxyErr := forceStopRecord(rootDir, LockFileName, PIDFileName, pidfile.KindProxy, timeout, &report)\n\n\tbackendReport := ForceStopReport{RecordPath: pidfile.Path(rootDir, server.PIDFileName)}\n\tbackendErr := forceStopRecord(\n\t\trootDir,\n\t\tserver.LockFileName,\n\t\tserver.PIDFileName,\n\t\tpidfile.KindDoltBackend,\n\t\ttimeout,\n\t\t&backendReport,\n\t)\n\tif backendReport.RecordFound || backendErr != nil {\n\t\treport.Backend = &backendReport","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/dbproxy/proxy/force_stop.go#L47-L83","documentation":"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.","triggerScenarios":"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)).","commonSituations":"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'.","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"],"exampleFix":"// before\nreport, err := proxy.ForceStopUnverified(rootDir, proxy.WithTimeout(cfg.StopTimeout)) // cfg.StopTimeout == 0\n// after\ntimeout := cfg.StopTimeout\nif timeout <= 0 {\n    timeout = 30 * time.Second\n}\nreport, err := proxy.ForceStopUnverified(rootDir, proxy.WithTimeout(timeout))","handlingStrategy":"validation","validationCode":"func safeTimeout(d time.Duration) time.Duration {\n    if d <= 0 {\n        return 30 * time.Second\n    }\n    return d\n}\n// usage: proxy.WithTimeout(safeTimeout(cfg.StopTimeout))","typeGuard":"func validTimeout(d time.Duration) bool { return d > 0 }","tryCatchPattern":"report, err := proxy.ForceStopUnverified(rootDir, opts...)\nif err != nil && strings.Contains(err.Error(), \"timeout must be positive\") {\n    report, err = proxy.ForceStopUnverified(rootDir) // use library default\n}","preventionTips":["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"],"tags":["validation","api-misuse","timeout","argument-error"],"backgroundTag":"invalid-argument-value","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}