gastownhall/beads · warning
ErrUnverifiableProcess
ErrUnverifiableProcess
Error message
proxy process identity is unverifiable
What it means
ErrUnverifiableProcess marks lifecycle operations (stop/force-stop) that refuse to signal a process because its workspace-scoped identity (pidfile PID + birth token) could not be verified. It is wrapped by unverifiableLifecycleError which supplies the detail message; callers use errors.Is to offer a narrower recovery path without misclassifying unrelated shutdown failures.
Source
Thrown at internal/storage/dbproxy/proxy/endpoint.go:98
quarantineRetention = 30 * 24 * time.Hour
spawnMarkerFileName = "proxy.spawn"
stopEpochFileName = "proxy.stop-epoch"
)
var ResolveExecutable = os.Executable
var (
verifyProcessIdentity = procid.Verify
resolveRootIdentity = identity.RootID
readControlSecret = identity.ReadSecret
)
// ErrUnverifiableProcess marks lifecycle operations that refuse to signal a
// process because its workspace-scoped identity cannot be verified. Callers
// may use errors.Is to offer a narrower recovery path without treating
// unrelated shutdown failures as identity failures.
var ErrUnverifiableProcess = errors.New("proxy process identity is unverifiable")
type unverifiableLifecycleError struct {
message string
}
func (e *unverifiableLifecycleError) Error() string {
return e.message
}
func (e *unverifiableLifecycleError) Unwrap() error {
return ErrUnverifiableProcess
}
var stopEpochSequence atomic.Uint64
// beforeProxyChildStart is a deterministic test hook for the otherwise tiny
// release-lock-before-exec scheduling window. Production leaves it as a no-op.
var beforeProxyChildStart = func() {}View on GitHub (pinned to 71377f2769)
Solutions
- Treat it as a safety refusal: use errors.Is(err, ErrUnverifiableProcess) and take the explicit force-stop/unverified recovery path the API exposes
- Remove the stale pidfile so the next start does a clean spawn instead of trying to stop an unverified process
- Investigate what process holds the port before force-stopping; do not blindly kill
Example fix
// before
if err := ep.Stop(ctx); err != nil { return err }
// after
if err := ep.Stop(ctx); err != nil {
if errors.Is(err, endpoint.ErrUnverifiableProcess) {
return ep.ForceStopUnverified(ctx) // explicit user-approved path
}
return err
} Defensive patterns
Strategy: type-guard
Validate before calling
if !pidfileMatchesProcess(pf) { /* skip or quarantine instead of signaling */ } Type guard
func isUnverifiable(err error) bool { return errors.Is(err, endpoint.ErrUnverifiableProcess) } Try / catch
if err := ep.Stop(ctx); err != nil {
if errors.Is(err, endpoint.ErrUnverifiableProcess) {
return handleUnverified(ep) // explicit recovery path
}
return err
} Prevention
- Avoid PID reuse confusion by cleaning stale pidfiles promptly
- One manager per workspace
- Never signal a process you have not verified against pidfile+birth
When it happens
Trigger: Attempting to stop a proxy whose live process cannot be matched against the pidfile's birth token; shutdown hitting a foreign or recycled-PID process; CanForceStopUnverified gating refusals for unverified records.
Common situations: PID reuse after a crash so the pidfile PID now belongs to an unrelated process; shared workspace directory with another checkout's proxy; stale pidfile pointing at a foreign listener.
Related errors
- procid: process %d no longer matches token
- capture proxy birth identity: %w
- kill verified pid %d from %s: %w
- close verified process handle for pid %d: %w
- confirm verified pid %d stopped: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/4fb9b9369ac6cd9d.
Report an issue: GitHub.