gastownhall/beads · error

authenticated proxy identity does not match its pidfile or…

Error message

authenticated proxy identity does not match its pidfile or workspace

What it means

This error is returned by readAndDial when the freshly spawned/authenticated proxy's handshake reply does not match the pidfile or workspace (mismatched PID, data/control port, or UpstreamID). It indicates the process that answered is not the proxy this workspace owns, so adoption is refused and the record is quarantined.

Solutions

  1. Let the library quarantine the record (default behavior) and retry the start to get a clean spawn on a fresh port
  2. Check for port collisions with other workspaces or leftover processes (lsof/ss) and kill/avoid them
  3. Ensure only one beads instance manages the workspace at a time (use the stop-epoch/lock protocol)

Example fix

// before
res := readAndDial(rootDir, pf)
// caller assumes success
// after
res := readAndDial(rootDir, pf)
if res.status == adoptionIdentityMismatch {
    // library already quarantined the foreign record; restart cleanly
    return spawnFreshProxy(rootDir)
}
Defensive patterns

Strategy: type-guard

Validate before calling

// before adopting, verify the listener identity
if !pidMatchesPidfile(reply.PID, pf) || reply.UpstreamID != pf.UpstreamID { return errForeignListener }

Type guard

func isIdentityMismatch(status adoptionStatus) bool { return status == adoptionIdentityMismatch }

Try / catch

res := readAndDial(rootDir, pf)
if res.status == adoptionIdentityMismatch {
    // record already quarantined; start a clean proxy
    return spawnFreshProxy(rootDir)
}

Prevention

When it happens

Trigger: readAndDial adoption probe where reply.PID, DataPort, ControlPort, or UpstreamID differs from the pidfile values; a foreign listener answering on the expected port.

Common situations: Another process grabbed the port between spawn and handshake; PID reuse; two workspaces/instances sharing a port range; stale pidfile pointing at someone else's server.

Understand the failure class

Related errors


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

Appendix: source

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

	reply, err := identity.Identify("127.0.0.1", pf.ControlPort, secret, identityProbeTimeout)
	if err != nil {
		return adoptionResult{status: adoptionIdentityMismatch, pidfile: pf, err: err}
	}
	// Accept schema v2 or newer, matching pidfile.ValidateV2's forward-compat
	// policy for records.
	if reply.Schema < pidfile.SchemaV2 ||
		reply.Role != pidfile.KindProxy ||
		reply.RootID != expectedRootID ||
		reply.RootID != pf.RootID ||
		reply.PID != pf.Pid ||
		reply.Birth != pf.Birth ||
		reply.DataPort != pf.Port ||
		reply.ControlPort != pf.ControlPort ||
		reply.UpstreamID != pf.UpstreamID {
		return adoptionResult{
			status:  adoptionIdentityMismatch,
			pidfile: pf,
			err:     errors.New("authenticated proxy identity does not match its pidfile or workspace"),
		}
	}

	ep := Endpoint{Host: "127.0.0.1", Port: pf.Port}
	if !probePort(ep, identityProbeTimeout) {
		return adoptionResult{
			status:  adoptionIdentityMismatch,
			pidfile: pf,
			err:     fmt.Errorf("authenticated proxy data port %d is not accepting connections", pf.Port),
		}
	}
	return adoptionResult{status: adoptionAdopted, endpoint: ep, pidfile: pf}
}

func probePort(ep Endpoint, timeout time.Duration) bool {
	conn, err := net.DialTimeout("tcp", ep.Address(), timeout)
	if err != nil {
		return false

View on GitHub (pinned to 71377f2769)