The pairing code is lost on reload, and Hermes will not resend it for ten minutes #38

Closed
opened 2026-07-30 10:20:11 +00:00 by aiko · 1 comment
Owner

The pairing code lives in React state only (initialPairing, src/renderer/src/services/pairing.ts:121-127). Nothing persists it. Reload, HMR, app restart, or a webview reload on Android and it is gone — while the code itself is still valid for an hour and still the only string an operator can approve with.

Asking again does not get it back, because Hermes will not answer for ten minutes:

  • generate_code calls _record_rate_limit on success (hermes-agent/gateway/pairing.py:467), RATE_LIMIT_SECONDS = 600 (:48).
  • The rate-limited path returns None silentlyif self.pairing_store._is_rate_limited(...): return None (hermes-agent/gateway/run.py:12261-12262). No message reaches the device at all.

So after a reload the 周波数 screen shows CODE_ABSENT (「まだ合図が届いていない」) and stays there for up to ten minutes with nothing on the wire to explain it. The code that would have worked is unrecoverable from the client's side.

services/pairing.ts:78-82 documents the ten-minute limit correctly and then reasons only about the case where the page stays alive.

What is needed

  • Persist code, command and codeAt (same useLocalStorage the onboarded flag already uses), expiring at the code's real TTL — CODE_TTL_SECONDS = 3600 (hermes-agent/gateway/pairing.py:47).
  • A restored code must still be displaced by a newer offer, and must still clear on paired.
  • Do not persist phase as paired. Approval is proven by a turn; a stored "paired" would be exactly the forged-grant shape ADR-0001 and #28 both close.

Acceptance

  • A code survives a reload of the client and is still displayed with its elapsed wait
  • A stored code older than its TTL is dropped rather than shown
  • A newer offer replaces a stored one
  • paired is never restored from storage — it is re-proven by a turn
  • Test: reload with a stored code renders the code, not 「まだ合図が届いていない」
The pairing code lives in React state only (`initialPairing`, `src/renderer/src/services/pairing.ts:121-127`). Nothing persists it. Reload, HMR, app restart, or a webview reload on Android and it is gone — while the code itself is still valid for an hour and still the only string an operator can approve with. Asking again does not get it back, because Hermes will not answer for ten minutes: - `generate_code` calls `_record_rate_limit` **on success** (`hermes-agent/gateway/pairing.py:467`), `RATE_LIMIT_SECONDS = 600` (`:48`). - The rate-limited path returns `None` **silently** — `if self.pairing_store._is_rate_limited(...): return None` (`hermes-agent/gateway/run.py:12261-12262`). No message reaches the device at all. So after a reload the 周波数 screen shows `CODE_ABSENT` (「まだ合図が届いていない」) and stays there for up to ten minutes with nothing on the wire to explain it. The code that would have worked is unrecoverable from the client's side. `services/pairing.ts:78-82` documents the ten-minute limit correctly and then reasons only about the case where the page stays alive. ## What is needed - Persist `code`, `command` and `codeAt` (same `useLocalStorage` the onboarded flag already uses), expiring at the code's real TTL — `CODE_TTL_SECONDS = 3600` (`hermes-agent/gateway/pairing.py:47`). - A restored code must still be displaced by a newer offer, and must still clear on `paired`. - Do not persist `phase` as `paired`. Approval is proven by a turn; a stored "paired" would be exactly the forged-grant shape ADR-0001 and `#28` both close. ## Acceptance - [ ] A code survives a reload of the client and is still displayed with its elapsed wait - [ ] A stored code older than its TTL is dropped rather than shown - [ ] A newer offer replaces a stored one - [ ] `paired` is never restored from storage — it is re-proven by a turn - [ ] Test: reload with a stored code renders the code, not 「まだ合図が届いていない」
Author
Owner

Superseded by ADR-0011, devices pair to the go-between, not to Hermes (denpa/docs/adr/0011-devices-pair-to-the-go-between.md, commit 04a3249).

This issue exists because the pairing code is minted by Hermes, which rate-limits pairing responses to one per user per ten minutes and returns nothing at all when rate-limited — so a client that lost the code in a reload could not get it back. Persisting the code was the way to survive that.

Under ADR-0011 the go-between mints enrollment codes itself. There is no ten-minute silence to survive, no household budget to lose a code into, and the code's lifetime is minutes rather than an hour because the operator is standing there when they ask for it. The client no longer holds Hermes-issued pairing state at all.

Closing rather than reworking: what remains of the concern — a device surviving a reload mid-enrollment — belongs to the enrollment flow's own design, not to persisting something Hermes said.

The work in flight for this was stopped and preserved at cf7d0e0 on issue/denpa-37-39-pairing-slice, unreviewed and unmerged.

Superseded by ADR-0011, *devices pair to the go-between, not to Hermes* (`denpa/docs/adr/0011-devices-pair-to-the-go-between.md`, commit `04a3249`). This issue exists because the pairing code is minted by **Hermes**, which rate-limits pairing responses to one per user per ten minutes and returns nothing at all when rate-limited — so a client that lost the code in a reload could not get it back. Persisting the code was the way to survive that. Under ADR-0011 the go-between mints enrollment codes itself. There is no ten-minute silence to survive, no household budget to lose a code into, and the code's lifetime is minutes rather than an hour because the operator is standing there when they ask for it. The client no longer holds Hermes-issued pairing state at all. Closing rather than reworking: what remains of the concern — a device surviving a reload mid-enrollment — belongs to the enrollment flow's own design, not to persisting something Hermes said. The work in flight for this was stopped and preserved at `cf7d0e0` on `issue/denpa-37-39-pairing-slice`, unreviewed and unmerged.
aiko 2026-07-30 12:48:01 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
aiko/denpa#38
No description provided.