gastownhall/beads · error

root identity mismatch (record has %q, workspace has %q)

Error message

root identity mismatch (record has %q, workspace has %q)

What it means

The backend PID record's RootID does not match the current workspace's root identity. This means the record belongs to a different workspace (or the workspace was re-created/moved), so the recorded process cannot be trusted to be this workspace's backend. Cleanup refuses with an unverifiableProcessError rather than killing a possibly foreign process.

Source

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

			"backend cleanup",
			recordPath,
			pf.Pid,
			err,
			unverifiableProcessChecks{
				LiveEstablished: live,
			},
		)
	}
	expectedRootID, err := identity.RootID(rootDir)
	if err != nil {
		return fmt.Errorf("resolve backend root identity: %w", err)
	}
	if pf.RootID != expectedRootID {
		return unverifiableProcessError(
			"backend cleanup",
			recordPath,
			pf.Pid,
			fmt.Errorf("root identity mismatch (record has %q, workspace has %q)", pf.RootID, expectedRootID),
			unverifiableProcessChecks{},
		)
	}

	handle, dead, err := openRecordedProcess(pf)
	if err != nil {
		return unverifiableProcessError(
			"backend cleanup",
			recordPath,
			pf.Pid,
			err,
			unverifiableProcessChecks{},
		)
	}
	if dead {
		if _, err := quarantineRecord(rootDir, server.PIDFileName, time.Now()); err != nil {
			return fmt.Errorf("quarantine dead backend record: %w", err)
		}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Confirm no live dolt-backend process belongs to this directory (ps / tasklist for the recorded pid).
  2. Manually quarantine the stale record: mv <recordPath> <recordPath>.stale-<unix-timestamp>, then retry cleanup.
  3. Avoid copying or renaming workspaces while a backend is running; stop the backend first.
  4. If workspaces share a directory via symlink/bind-mount, separate them so each has its own pidfile.

Example fix

// before
Error: backend cleanup refused for unverifiable process pid 4242 ...: root identity mismatch (record has "abc123", workspace has "def456")
// after
$ ps -p 4242            # confirm not yours / already dead
$ mv /ws/.beads/dbproxy/dolt-backend.pid /ws/.beads/dbproxy/dolt-backend.pid.stale-$(date +%s)
$ bd doctor
Defensive patterns

Strategy: validation

Validate before calling

// Before cleanup, verify the record belongs to this workspace:
expected, _ := identity.RootID(rootDir)
var rec struct{ RootID string }
if data, err := os.ReadFile(recordPath); err == nil && json.Unmarshal(data, &rec) == nil && rec.RootID != expected {
    os.Rename(recordPath, recordPath+".stale-"+fmt.Sprint(time.Now().Unix())) // quarantine foreign record
}

Type guard

func recordBelongsToWorkspace(recRootID, wsRootID string) bool { return recRootID == wsRootID }

Try / catch

var ule *unverifiableLifecycleError
if err := cleanupOrphanBackend(rootDir); errors.As(err, &ule) && strings.Contains(ule.Error(), "root identity mismatch") {
    // foreign record: confirm recorded pid is not ours, then mv record to .stale-<ts>
    return nil
}

Prevention

When it happens

Trigger: pf.RootID != identity.RootID(rootDir) at internal/storage/dbproxy/proxy/endpoint.go:809-816 — the pidfile at <rootDir>/<PIDFileName> was written for a different workspace root than the one being cleaned.

Common situations: Workspace directory was copied, moved, or cloned (carrying a foreign pidfile); two workspaces sharing storage via symlink; workspace deleted and re-created while an old pidfile remained; restoring the workspace from a backup.

Related errors


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