Skip to content

Home network: compare both machines' networks over the relay (#219) - #221

Merged
bfieser merged 9 commits into
mainfrom
sync/219-lan-compare
Nov 7, 2026
Merged

bfieser merged 9 commits into
mainfrom
sync/219-lan-compare

Conversation

@bfieser

@bfieser bfieser commented Nov 6, 2026

Copy link
Copy Markdown
Collaborator

Closes #219. Stacked on #218: this PR's base is sync/216-lan-diagnosis. Please review and merge #218 first; this PR then retargets to main.

Why

#218 lets a machine say why it can't find a linked machine on the home network, but only from what it can see itself. One machine alone can't tell that the other one is on another Wi-Fi, has a VPN on, isn't using Home network sync, is asleep, or can't hear it. The other machine can.

What the user sees

New per-machine reasons on the Sync & backup line and in its details:

  • "Home network sync isn't on there"
  • "can't open its network port" (it's picked there, but the port won't open)
  • "it last checked in 3 h ago. Make sure it's awake, with Patch Pilot open and online." (accent tone)
  • "a VPN is on there"; "its messages go to a virtual network on it (vEthernet (WSL))"
  • "it's on a different network", including the same 192.168.1.x range behind another router
  • "it hears this machine, but nothing it sends gets here. Allow Patch Pilot through the firewall on this machine."
  • "both are on the same network, but neither hears the other. A firewall… or the router keeps devices apart (client isolation)"
  • "Desktop can't hear this machine, so this machine's files aren't reaching it" (shown on the machine that is heard)

A wrong reason is worse than none. A reason is named only when both machines' data agree and the other machine's report is current. "Current" means fresh, reached the relay after this machine last heard that machine, and this machine is online and hasn't just woken from sleep. Advice about this machine's own network (e.g. "set it to Private") is left out while the missing machine may be on another network.

How

  • net/<machine id>: a small sealed relay object (crates/local/src/net_report.rs) holding:

    • whether Home network is on and listening, and since when
    • subnets (network address and prefix only)
    • whether broadcasts or internet traffic go through a VPN, or into a virtual adapter
    • whether the last broadcast failed
    • which linked machines it hears now
    • its public address as the relay saw it

    It's separate from the machine/ bundle so network changes don't re-send the scan and every Primary Rig version.

  • When it's sent (app/src-tauri/src/lan.rs): on change (at most every 30 s); every 5 min while a machine that might still turn up is missing; every minute while a machine here can't hear this one. Nothing is sent while everything works. A report counts as sent only once the relay took it, so failures retry.

  • Relay (app/src-tauri/src/sync.rs): reads each linked machine's report once per session (one row read each, decision Release 0.4.2, with "Bring over all Reaper scripts" #113), then on live changed events.

  • Connection health (sync.rs): the live connection now counts as offline when a ping goes unanswered. Waking from sleep restarts the session, so reports from before sleep are never judged as current.

  • Rules (crates/local/src/lan_diagnosis.rs): why() gives the per-machine reason, deaf lists machines that can't hear this one, and deaf_cause says why when this machine can tell. No timing rule compares two machines' clocks.

  • UI: app/src/Backup.tsx and app/src/api.ts. The line and the details use the same rule for which reason wins.

Relay change (deploys from main via relay.yml)

relay/src/index.ts:

  • A PUT now answers with the client's address (ip, from CF-Connecting-IP).
  • A GET adds x-age (how long the relay has held the object).

The relay sees the address anyway and stores nothing more. Older apps ignore both. Against the currently deployed relay this PR still works; it just has no public address (so no "same range, other router" or "blocked") and judges report age by when it fetched.

Decision

#124 is Proposed. It is the last row of docs/decisions.md and spells out every rule and threshold. Please look at it in particular: it adds a relay change, and it shares subnets and public addresses between the user's own machines, end-to-end encrypted like everything on the relay.

Review

Before opening this, the change went through four review rounds: one independent reviewer, then three multi-agent rounds in which each finding was checked by separate skeptics. That turned up about 40 real issues, mostly reasons that could be wrong in edge cases (sleep, quick restarts, the same address range at a café, IPv4 vs IPv6, VPN variants, clocks). All are fixed and covered by tests.

Testing

  • cargo fmt --check, cargo clippy -D warnings and cargo test on both workspaces: 486 core tests, 26 new; app tests pass.
  • npm run build (app); npm test + typecheck (relay, 14 tests, 1 new).
  • All 6 relay integration tests pass against a local wrangler dev, including the new "reports travel with their age" and "the live connection answers pings".
  • Not tested yet: the real two-machine flow, and macOS. Brian will try it with the laptop on another network and on a VPN.

🤖 Generated with Claude Code

bfieser and others added 8 commits November 6, 2026 10:47
When Home network sync can't find a linked machine, the Sync & backup
line now names it and, when this machine can tell, why: a VPN or virtual
adapter carrying the broadcasts, no network, Windows calling the network
Public, macOS Local Network access off, another Patch Pilot version, or
another rig's Patch Pilot on the network. It no longer says "In sync"
while nobody is here. The details list each machine and a checklist.

- patchpilot_local::lan_diagnosis: pure diagnose() over gathered facts,
  adapter classification (name and Windows description), egress kind,
  bind error wording; tested with hand-built facts.
- patchpilot_local::netinfo: read-only probes (UDP connect + local_addr,
  sysinfo interfaces, PowerShell adapter description and profile with a
  5 s deadline at below-normal priority).
- lan::read tells this rig, another version, another rig and noise apart.
- sync.rs keeps the relay's list of linked machines, so machines added
  from an export file or removed never show as missing.
- lan.rs gathers facts on the existing 10 s beacon thread, looks at the
  network only while a machine is missing and none is here, and tells
  the UI only when the diagnosis changes.

Decision #123 (amends #94).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each linked machine keeps a small sealed net/<machine id> report on the
relay: Home network on, its subnets, whether its broadcasts leave through a
VPN, and which linked machines it hears. Sent on change and, only while a
machine is missing or can't hear this one, every 5 minutes. The others use
it to say why they can't find that machine: Home network off there, asleep
or closed, VPN there, a different network, one-way hearing (firewall), or
the same network with neither hearing the other. Decision #124 (Proposed).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A matching subnet alone no longer counts as the same network: most
  routers use the same few ranges. The relay now answers a PUT with the
  client's address, and "blocked" needs the same public address too
  (same family, neither side's internet through a VPN); a different one
  means "different network".
- Report age is judged by when it reached the relay, on this machine's
  clock (relay x-age header), never the other machine's clock.
- "Can't hear this machine" waits until the other machine had listened
  30 s when it made its report, so a quick restart isn't deafness.
- Peers count as here by the wall clock (a Mac's Instant stops in sleep).
- A port that won't open is "can't listen", not "Home network off".
- A definite reason from the other machine beats this machine's guesses;
  the deaf line names only this machine's own VPN/virtual adapter.
- A report counts as sent only once the relay took it, so failures retry.
- Network facts older than 90 s are left out of reports, and a machine
  heard here lately is never called quiet.
- sync no longer assumes the home-network state exists at startup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A missing machine's report names a reason only if it reached the relay
  after this machine last heard that machine, and while this machine is
  online: a report made before it slept, quit or moved said "hears this
  machine" and was read as a firewall problem.
- "It hears this machine" and "blocked" need its own sends known fine (no
  VPN, no failed broadcast) and a report made since this machine started
  listening.
- A virtual adapter there is its own reason, not "a VPN".
- An unknown report age (older relay) names only "off"; "last checked in"
  counts from when the machine was last heard if that's later.
- Machines whose report says Home network is off no longer keep the
  others heartbeating or looking at their network.
- Windows adapter descriptions are looked up whenever the network is
  looked at (cached 10 min), so a VPN called "Ethernet 4" isn't missed.
- Network facts and their schedule use the wall clock; listening_since is
  fixed once, so a healthy machine's report doesn't look changed.
- The public address is forgotten when subnets change, and a report is
  sent again at once when an upload reveals a new one; no report is asked
  for while offline (the session start sends one).
- Line and details agree on which cause or reason is shown.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- "It hears this machine" needs a report made at least 55 s after this
  machine last heard it and after this run started (its list keeps a
  machine 45 s); other reasons need 20 s, two beacons, not one.
- "Different network" by subnets comes before "it hears this machine", so
  a machine that moved isn't read as a firewall problem.
- "Blocked" and "it hears this machine" also need this machine's own
  broadcasts to go out fine; a send that fails without an OS code counts.
- The public address is forgotten at the start of each relay session.
- The live connection counts as offline when a ping goes unanswered, so
  reports aren't trusted over a connection that died silently; a stale
  report nudge doesn't put the same report twice.
- This machine's network facts older than 90 s are ignored by the
  diagnosis too; a clock stepped back means "look again".
- An adapter Windows couldn't describe counts as unknown (retried each
  minute), not the home network.
- A cause here gives way when every missing machine has a reason on its
  own side (incl. another network), so a laptop at a café isn't told to
  make its Wi-Fi Private; one that gave way marks nothing on the checklist.
- Missing and deaf at once is said plainly; the deaf cause is found then.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- This machine's VPN explains a machine here that can't hear it, in its
  own field; it's no longer named as why another machine is missing.
- Advice about this machine's network (Public, a VPN for the internet) is
  left out while a missing machine reports from a network this machine
  can't confirm is its own: a laptop at a café isn't told to make the
  café's Wi-Fi Private, even when the addresses can't be compared.
- Waking from sleep (a beacon round over 30 s late) drops the reports read
  before and restarts the relay session at once; nothing from before
  sleep counts as current until it's read again.
- One unanswered ping is enough to count the connection as dead (timed by
  the ticks themselves).
- A VPN carrying the other machine's internet traffic, on this range and
  hearing nothing, is named as a likely reason; "it hears this machine"
  needs no VPN for the internet on either side.
- While a machine here can't hear this one, reports go out every minute,
  and a fresh report that doesn't list this machine prompts a network look
  straight away, so "a firewall here" shows in about a minute, not six.
- A Windows adapter with no description counts as unknown even when its
  network profile is known.
- Machines whose report says Home network is off aren't "in sync"; with
  someone here, each missing machine's own reason is in the line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@bfieser
bfieser deleted the branch sync/216-lan-diagnosis November 7, 2026 02:55
@bfieser bfieser closed this Nov 7, 2026
@bfieser bfieser reopened this Nov 7, 2026
@bfieser
bfieser changed the base branch from sync/216-lan-diagnosis to main November 7, 2026 02:56
@bfieser
bfieser merged commit 02717a3 into main Nov 7, 2026
7 checks passed
@bfieser
bfieser deleted the sync/219-lan-compare branch November 7, 2026 03:03
joshuadav86 pushed a commit that referenced this pull request Nov 9, 2026
Brings in the home-network PRs (#218, #221, #223), which took decisions
#123-#125; this change is now decision #126.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Se1kg5gSsJs5yj2HeacfG
joshuadav86 added a commit that referenced this pull request Nov 9, 2026
Version bump and CHANGELOG entry for the Primary machine fix (#220,
decision #126) and the home-network PRs #218, #221 and #223 (decisions
#123-#125).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Se1kg5gSsJs5yj2HeacfG
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.

Home network: compare both machines' networks over the relay

1 participant