gastownhall/beads · warning

verify proxy pid %d: %w

Error message

verify proxy pid %d: %w

What it means

readAndDial wraps failures from verifyProcessIdentity when probing the OS identity (PID + birth token) of the process recorded in the proxy pidfile. Instead of treating this as a stale/dead record, discovery classifies it as adoptionUnverifiable: the recorded PID may be live but cannot be safely confirmed, so the proxy is not adopted.

Source

Thrown at internal/storage/dbproxy/proxy/endpoint.go:598

		return adoptionResult{status: adoptionNoRecord}
	}
	if err := pf.ValidateV2(pidfile.KindProxy); err != nil {
		if errors.Is(err, pidfile.ErrLegacySchema) {
			return adoptionResult{status: adoptionLegacy, pidfile: pf, err: err}
		}
		return adoptionResult{status: adoptionMalformed, pidfile: pf, err: err}
	}

	matched, err := verifyProcessIdentity(pf.Pid, procid.Token(pf.Birth))
	if err != nil {
		// Discovery must not turn an identity-probe failure into a fatal
		// pidfile I/O error. In particular, Windows ERROR_ACCESS_DENIED on a
		// recycled PID belongs in this non-adopting path; proxy.lock still
		// gates quarantine and replacement.
		return adoptionResult{
			status:  adoptionUnverifiable,
			pidfile: pf,
			err:     fmt.Errorf("verify proxy pid %d: %w", pf.Pid, err),
		}
	}
	if !matched {
		return adoptionResult{status: adoptionStaleDead, pidfile: pf}
	}

	expectedRootID, err := resolveRootIdentity(rootDir)
	if err != nil {
		return adoptionResult{status: adoptionUnverifiable, pidfile: pf, err: err}
	}
	secret, err := readControlSecret(rootDir)
	if err != nil {
		return adoptionResult{status: adoptionUnverifiable, pidfile: pf, err: err}
	}
	reply, err := identity.Identify("127.0.0.1", pf.ControlPort, secret, identityProbeTimeout)
	if err != nil {
		return adoptionResult{status: adoptionIdentityMismatch, pidfile: pf, err: err}
	}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check whether the PID in the pidfile belongs to a live process (ps -p <pid> / Task Manager) and stop the old proxy if it does
  2. Re-run under the same user that started the proxy, or align container/user namespaces
  3. Delete/quarantine the pidfile (bd will quarantine under proxy.lock and spawn a fresh proxy)
  4. Inspect the wrapped OS error via errors.Unwrap for the specific errno and address it (permissions, PID visibility)

Example fix

// before
matched, err := verifyProcessIdentity(pf.Pid, procid.Token(pf.Birth))
// caller side
discovery := readAndDial(root)
if discovery.status == adoptionUnverifiable {
    log.Printf("cannot verify pid %d: %v; refusing to adopt", discovery.pidfile.Pid, discovery.err)
}
// after: explicitly test identity before relying on an existing proxy
if pf, _ := pidfile.Read(root, pidfile.FileName("proxy")); pf != nil && !pidMatches(pf.Pid, pf.Birth) {
    quarantineRecord(root, PIDFileName, time.Now()) // force fresh spawn
}
Defensive patterns

Strategy: fallback

Type guard

func isUnverifiable(d adoptionResult) bool {
    return d.status == adoptionUnverifiable && d.err != nil
}

Try / catch

discovery := readAndDial(root)
if isUnverifiable(discovery) {
    // do NOT adopt; let proxy.lock-gated quarantine + fresh spawn handle it
    if err := quarantineForSpawn(root, discovery); err != nil { return err }
}

Prevention

When it happens

Trigger: pf.ValidateV2 succeeds and verifyProcessIdentity(pf.Pid, pf.Birth) returns an OS-level probe error — e.g. Windows ERROR_ACCESS_DENIED on a recycled PID, or OpenProcess/permission failures — before any identity matching can occur.

Common situations: PID was recycled by an unrelated process owned by another user (Windows access denied); running bd across user boundaries or in a container where the recorded PID is not visible; hardened security software denying process opens.

Related errors


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