Closed general

Cross-box permission prompts never reach the embedded Hydrogen: render_mirrored_request shows a toast + ab card only (John saw the toast, nothing in ah); stale remote cards never dismissed

John Lauer · 2mo ago ·closed by John Lauer

John, 2026-09-08, on AdomLapper (ab 2.1.72 embedded under Hydrogen 1.0.281): "I just saw this ab toast ('Permission request on winvm ... Open Adom Bridge to decide it here') but didn't see anything in ah." Confirmed and measured:

  • AdomLapper ab desktop_ui_state: 4 cards, all remote: {machine: winvm, via: relay} (3 shell, 1 grant), i.e. the cross-box prompts arrived and ab rendered them in ITS window and as a Windows toast.
  • AdomLapper Hydrogen main window DOM: 0 .perm-card. Hydrogen's control log has no /permission/request for them.
  • On the ORIGIN box (winvm) the same prompts DID reach Hydrogen: "SHELL COMMAND / ON WINVM" with the decoded command and Once/1h/24h/Auto/Deny, "GRANT REQUEST / ON WINVM", and a notify-decision "DECISION / ON WINVM" with the verbatim buttons. So the local mirror works; the cross-box one never reaches ah.

Why (2.1.72 source): permission_mirror::request calls hd_bridge::mirror_permission_request when embedded, then fans out to mesh and relay. On the RECEIVING box, mesh::render_mirrored_request (used by both the mesh accept loop and the relay perm_mirror_request handler) shows a toast, emits shell-approval-request to ab's own window and logs "MIRROR: permission prompt from {peer}", but never calls hd_bridge::mirror_permission_request. The one exception is the firewall notice (commands.rs ~2390), which is forwarded explicitly. Everything hydrogen#66 describes ("Render the box name loudly ... A decision made in ah's dialog runs on that box") assumes the card gets to ah in the first place; today it does not.

This is exactly the Ray case: Windows toasts hidden, ab in the tray, so a prompt raised on any other of his boxes is invisible.

Fix: in render_mirrored_request, after building the composite body (id remote:<peer>:<realId>, remote{...}, machine), forward it to ah when embedded (hd_bridge::mirror_permission_request(body.clone())), and forward the matching mesh-permission-dismiss / perm_mirror_dismiss as hd_bridge::mirror_permission_dismiss(composite, decision). ah already renders the machine chip and posts /permission/resolve {id, decision} back to the local ab; ab's resolve must accept the composite id and route it through mesh_resolve_permission (realId + resolveNonce), the same path its own card click uses. desktop_ui_decide shows that path exists.

Second observation from the same data: the 4 remote cards on AdomLapper are STALE. The gated relay calls on winvm returned needs_approval at once and their prompts were gone from winvm's stack within seconds (Hydrogen's self-heal closed them: "no longer pending on AB"), but no dismiss reached the laptop, so ab's card stack there still lists them 20 minutes later. Either the origin should send perm_mirror_dismiss when it drops a request without a decision (expiry), or receivers should apply the same 330 s expiry cap Hydrogen uses.

Evidence: winvm Hydrogen DOM dumps and screenshots in hydrogen-desktop docs/preprod/preprod-1.0.280-2026-09-07.md (2026-09-08 mirror addendum).

2 Replies

John Lauer · 2mo ago

Verified fixed with ab 2.1.74 on every box (2026-09-08 15:41 UTC): a run_script raised on winvm from the container appeared in AdomLapper's Hydrogen (1.0.284) within 2.5 s as SHELL COMMAND / ON WINVM with the accent origin chip, alongside the live mesh-join cards (one local, one from ConfRoomROG via relay). AdomLapper's ab stack no longer holds the stale winvm cards either. From the Hydrogen side this is done; closing is yours.

John Lauer · 2mo ago

Shipped in 2.1.74 + 2.1.75 (insiders). Both halves, on the receiving box:

  • mesh::render_mirrored_request now forwards the composite card (remote:<peer>:<realId>, remote{}, machine) to the embedded Hydrogen through hd_bridge::mirror_permission_request, the same call a LOCAL prompt has always made, so ah renders it with the machine chip on every box you have Hydrogen open on. Both transports (mesh accept loop and relay perm_mirror_request) go through it.
  • /permission/resolve (and therefore desktop_ui_decide) accepts the composite id (2.1.75; in 2.1.74 the route answered already_resolved from its own pending lookup before the remote branch ran, caught in the live proof): ab looks the card up in its new mirrored-card registry (peer, realId, nonce), routes the decision to the origin through mesh_resolve_permission with that nonce, then drops the local copies. A decision for a card that is no longer pending here answers not_pending with the origin named, never a silent no-op.
  • Dismisses are forwarded too: the mesh perm_dismiss and relay perm_mirror_dismiss arms now call one helper (dismiss_mirrored) that drops ab's card AND ah's copy, carrying the decision.
  • Stale cards: a mirrored card nobody answers is dropped on the receiving box after 330 s (MIRROR_EXPIRY_SECS, the same cap Hydrogen applies), with an expired dismiss to ah. The origin already fans out a dismiss for a decided or waited-out request; the needs_approval refusal case (no waiter) is what the receiver cap covers.

The user-facing story is on the page now: docs/mesh.md ("In Hydrogen: the cards show up where you are looking"). hydrogen#66's ask to render the box name loudly is unchanged on ah's side; the card arrives with machine and remote.machine set.

  • 2.1.75 also lists mirrored ids in /permission/state (pending and pendingByKind.remote), so Hydrogen's self-heal keeps a forwarded remote card up instead of closing it as "no longer pending on AB". And the remote branch runs BEFORE the window-grant handling: a "1 hr" clicked in Hydrogen on winvm's card is a grant for winvm's shell, not the deciding box's.

Live proof, 2026-09-08 15:34 UTC, fleet on 2.1.75 (winvm origin, AdomLapper receiver, embedded under Hydrogen):

AdomLapper  10:34:52  MIRROR: permission prompt from winvm via relay (id 110c87a7...)
AdomLapper  10:34:52  PERMISSION-MIRROR: delivered type=shell-approval id=remote:winvm to ah
GET /permission/state -> pending: ["remote:winvm:110c87a7..."], pendingByKind.remote: [same]
POST /permission/resolve {id: "remote:winvm:110c87a7...", decision: "deny"}
  -> {ok:true, scope:"remote", origin:"winvm", decidedOn:"AdomLapper", originResult:{applied:"deny", scope:"request"}}
winvm       15:35:17  MIRROR: permission decision deny for id 110c87a7... from another machine
winvm       15:35:17  PERMISSION-MIRROR: dismiss id=110c87a7... (deny) delivered to ah
AdomLapper  10:35:16  MIRROR: card from winvm dismissed here (deny) / dismiss id=remote:winvm delivered to ah
John Lauer closed this · 2mo ago

Log in to reply.