Skip to content

fix: stable device fingerprint so prune finds old sessions - #340

Merged
upsetbit merged 8 commits into
masterfrom
fix/stable-device-fingerprint
Sep 22, 2026
Merged

upsetbit merged 8 commits into
masterfrom
fix/stable-device-fingerprint

Conversation

@upsetbit

Copy link
Copy Markdown
Contributor

Summary

prosa prune reported nothing to prune on machines whose macOS hostname changes with the network lease.

  • The device fingerprint on darwin is derived from scutil --get LocalHostName, falling back to os.Hostname() without .local. Linux is unchanged and ids computed from a stable hostname stay the same.
  • The store merges every devices row sharing the canonical device's machine_id into it, reassigning sessions and dropping the stale rows.
  • sync and prune bind the local device (upsert plus both rebinds) before any device-scoped read.
  • When no candidate exists but older sessions still lack a confirmed push, prune says so and points to prosa sync; the --json summary carries an unconfirmed count.
  • Docs describe the fingerprint, the rebind, and the sync-before-prune rule.

Out of scope, tracked separately: the server's push idempotency keys on session id and hash, so an unchanged session does not move to a new server device after a re-login.

Test plan

  • go build, go vet, gofumpt, go test -race -count=1 ./..., golangci-lint
  • Unit tests for the darwin resolver, the machine-id rebind, the unconfirmed count, and prune dry-run on sessions bound to a stale device

🤖 Generated with Claude Code

macOS os.Hostname follows the DHCP name, so the device id changed
across network leases. darwin now hashes scutil's LocalHostName.
A failed or empty lookup falls back to os.Hostname with one trailing
.local removed. Other platforms still use os.Hostname with that same
trim, so an existing stable hostname keeps its id. Fingerprint is
unchanged.
RebindDevicesByMachineID, in one transaction, moves sessions onto the
canonical device from every other devices row with the same non-empty
machine_id, then deletes those rows. sessions.device_id is the only
local column that references devices.id. An empty machine id matches
nothing, and a second call moves nothing.
sync and prune share bindLocalDevice. It upserts the current
fingerprint, moves seed local sessions onto it, then collapses stale
device rows for this machine. prune does this before listing
candidates, so sessions still filed under an older fingerprint are
visible without a separate sync.
concepts.md states the hostname and machine-id inputs of the device
fingerprint. store.md and cli.md describe RebindDevicesByMachineID
beside the seed-id rebind. Canonical session rows record DeviceID as
device.IDOnce.
CountOldUnconfirmed counts unpruned sessions on a device whose last
activity is before the cutoff and whose pushed_hash is NULL or differs
from raw_hash. ListPruneCandidates is unchanged.

Addresses R1-01.
SetResolveForTest replaces the cached fingerprint and restores the
previous resolver afterwards, in the same style as the resolve override.

Addresses R1-03.
When no candidate is confirmed, prune counts older sessions whose
pushed_hash is missing or stale and names prosa sync. The --json
summary records that count as unconfirmed.

Addresses R1-01.

The stale-device dry-run pins its machine id through SetResolveForTest.

Addresses R1-03.
prune lists sessions a prior sync confirmed. When older sessions still
lack that confirmation, the command reports the count.

Addresses R1-01.
@upsetbit
upsetbit merged commit bf29a63 into master Sep 22, 2026
5 checks passed
@upsetbit
upsetbit deleted the fix/stable-device-fingerprint branch September 22, 2026 18:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant