Skip to content

Handover asks live in the database, so a restart doesn't lose one (GRYT-2023) - #364

Merged
grytchat merged 2 commits into
mainfrom
claude/GRYT-2023-handover-asks-persist
Nov 3, 2026
Merged

grytchat merged 2 commits into
mainfrom
claude/GRYT-2023-handover-asks-persist

Conversation

@grytchat

@grytchat grytchat commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

When pp.sivert.io restarted for 1.12.7, it lost the handover your Mac had asked for. pendingAsks in dmKeys.ts was a Map in memory. The 30-day clock survived because it sits in users, but the request itself, the asking device's name and the prompt on the device with the key did not.

What changes

  • New table dm_key_asks (server_user_id, binding, device_name, created_at). Asking again keeps the first created_at. Five per account at most, and rows older than 30 days are deleted when the table is read.
  • An ask is named by the asking binding, not by the asker's socket id, which a restart throws away. It still goes out as askerId, so released clients send it straight back on deny.
  • dm:key:handover:pending replays from the table. The asking device gets its own ask back and ignores it, because both apps only show an ask on a device whose key matches the current one.
  • Deny removes the row and sends dm:key:handover:denied { binding } to the whole account, since there's no socket to aim at after a restart. The client PR makes only the asking device act on it.
  • A no-device ask (no current binding, or the same identity) now returns ok: false before telling anyone. It used to notify the other devices first, for an ask there was nothing to hand over for.
  • dm:key:reset:cancel ("that wasn't me") also removes the ask, so the prompt doesn't come back.

Look at

  • The migration in connection.ts and the cap query in dmKeyAsks.ts.
  • The deny broadcast. Released desktop clients mark their own pending ask as denied on any denied, so if two of your devices are both asking and one gets a no, both show it until the client PR ships.

Still open, not in this PR: an ask with a different binding replaces the reset and restarts its clock. Somebody with your password could keep a lost-device reset from ever finishing by asking from a fresh key every few weeks. That's the reset design from GRYT-1886, not this table, so it's its own task.

Tests: the three handover tests now ask on an account that has a key, and a new one checks that approving and "that wasn't me" both remove the row. All 20 pass.

🤖 Generated with Claude Code

…YT-2023)

pendingAsks was an in-memory Map. Restarting a server for 1.12.7 dropped the
handover Sivert's Mac had asked for on pp.sivert.io, with its device name, and
the device holding the key never heard about it again.

Asks now go in a dm_key_asks table: one row per account and asking key, with
the device name and when it first asked. They're named by the asking binding
instead of a socket id, which a restart also throws away. That name still goes
out as askerId, so older clients keep working. Approving, saying no or stopping
the reset removes the row, and so does expiry after 30 days. A no goes to every
device on the account, with the binding, and only the device that asked acts on it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The merge test checks every column that can hold a server user id. A guest's
asks were for the guest's own key, so they're deleted like its push devices, and
the device asks again as the account.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@grytchat
grytchat merged commit 3078393 into main Nov 3, 2026
5 checks passed
@grytchat
grytchat deleted the claude/GRYT-2023-handover-asks-persist branch November 3, 2026 23:04
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.

2 participants