Skip to content

fix: hand the peer the first epoch key before the first message, and answer a rekey only once stored - #361

Merged
Mearman merged 2 commits into
mainfrom
fix/notices-offline-flake
Oct 21, 2026
Merged

Mearman merged 2 commits into
mainfrom
fix/notices-offline-flake

Conversation

@Mearman

@Mearman Mearman commented Oct 21, 2026

Copy link
Copy Markdown
Member

The notices-offline e2e test failed intermittently because the console that posts the first notice sometimes held no content key yet. When the posting console (A) has the higher device id, the other console (B) mints epoch 1, and B did that in deliver() as a fire-and-forget room.rekey sent alongside its first message after joining. The reply could reach A before the rekey, and even when the rekey arrived first, core's createRoomRekeyHandler answered ok before onRekey stored the key. A rendered the reply, the test posted, postEncryptedNotice found no epoch, and the composer showed no content key held for room <path>: room.rekey this room before posting encrypted notices in red instead of listing the notice.

I reproduced it by running the spec repeatedly on a loaded machine with a copy that logged which side was lower and what the composer showed: every failure at this step had A as the higher participant, with that error under the composer. Whichever side is lower is decided by randomly generated device ids, which is the source of the nondeterminism.

Two changes close the window by construction:

  • core: createRoomRekeyHandler awaits onRekey before answering ok, so ok means the recipient holds the key. A store that fails now answers store_failed and rejects, where before the sender was told ok, marked the key delivered and never sent it again.
  • web-console: the first message after a join waits for the first-epoch delivery attempt, so the peer holds the key before it can read that message. A refused delivery (the peer holds no token yet) still lets the message through, as before.

The new unit tests drive the interleaving directly: a hub whose room.rekey answer is held back shows that no room.send goes out until it is answered, and a handler whose onRekey is still pending has not answered. Both fail on the previous code (I reverted each change and watched them fail) and pass with it.

The same repeated runs also showed two failures at later steps of this spec, unrelated to this race: the reopened console's Connect button not appearing within the action timeout, and the reopened console not showing the offline notice within the step timeout. Neither is addressed here.

Fixes #338

createRoomRekeyHandler answered "ok" before onRekey stored the unwrapped keys,
so a sender could act on the answer while the recipient still held no key,
and a store that failed after the answer left the sender believing the key was delivered.
The handler now awaits onRekey before answering "ok",
and answers store_failed and rejects when onRekey rejects.

Refs #338
…message

After joining a conversation, the participant that mints epoch 1 sent the room.rekey
and its first message at the same time, so the message could reach the peer first.
A peer that posted a notice as soon as it read that message held no content key yet
and the post failed with "no content key held for room".
The first message after a join now waits for the key delivery attempt,
which the peer answers only once the key is stored;
a refused delivery still lets the message through.

Fixes #338
@Mearman
Mearman marked this pull request as ready for review October 21, 2026 12:54
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
🔒 Security Review ⚠️ Failed 2026-10-21T12:55:29.747614Z
We couldn’t complete this review. Comment @codex security review to try again.
3132dee Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

This security review couldn’t start because the paying account has exhausted its Codex allowance and has insufficient available credits. The payer can check their balance and add credits in the Codex usage dashboard, then try again. If you do not manage the paying account, contact this repository's admins.

@Mearman

Mearman commented Oct 21, 2026

Copy link
Copy Markdown
Member Author

@codex security review

@chatgpt-codex-connector

Copy link
Copy Markdown

This security review couldn’t start because the paying account has exhausted its Codex allowance and has insufficient available credits. The payer can check their balance and add credits in the Codex usage dashboard, then try again. If you do not manage the paying account, contact this repository's admins.

@Mearman
Mearman merged commit 33d06e8 into main Oct 21, 2026
9 checks passed
@Mearman
Mearman deleted the fix/notices-offline-flake branch October 21, 2026 12:56
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.

notices-offline e2e: a console's own posted notice not visible within the step timeout, intermittently

1 participant