Skip to content

A friend request stays answerable for 30 days, across restarts - #49

Merged
TeoSlayer merged 1 commit into
mainfrom
fix/outgoing-request-lifetime
Oct 8, 2026
Merged

TeoSlayer merged 1 commit into
mainfrom
fix/outgoing-request-lifetime

Conversation

@TeoSlayer

Copy link
Copy Markdown
Contributor

Problem

An acceptance is honoured only while the requester still has the matching outgoing entry, and that entry lived 10 minutes (2 × handshakeMaxAge), in memory only. People accept friend requests when they see them, often hours later. By then the requester had forgotten asking, so the accept was dropped as "unsolicited": the peer trusted the requester, but not the other way round, and the requester showed the friend as waiting forever. A restart in between lost the request the same way.

Seen in production on Pilot Cloud on 2026-10-08: a request was accepted hours after it was sent and stayed "Waiting" until it was sent again. The peer then auto-accepted it, because it already trusted the requester.

Change

  • Lifetime: outgoing requests live outgoingRequestTTL = the peer's pendingHandshakeTTL (30 days).
  • Saved: they're stored in trust.json alongside the trusted, pending and revoked state, and restored on load. Expired ones are dropped.
  • Bounded: at most maxOutgoingRequests (4096); the oldest is forgotten first.
  • Removals stay saved: every removal goes through dropOutgoingLocked, which marks the state for saving, so a cancelled, rejected or completed request doesn't come back after a restart.

Tests

  • The reaper test that encoded the 10-minute rule now checks the new boundary.
  • New zz_outgoing_lifetime_test.go covers:
    • an acceptance hours later, direct and relayed
    • a restart in between
    • a cancelled request across a restart
    • the bound
  • go test -race ./... and go vet are clean.

🤖 Generated with Claude Code

An acceptance is honoured only while the requester still has the matching
outgoing entry, and that entry lived 10 minutes (2 x handshakeMaxAge), in
memory only. People accept friend requests when they see them, often
hours later, and by then the requester had forgotten asking: the accept
(direct or relayed) was dropped as "unsolicited", the peer trusted the
requester but not the other way round, and the requester showed the
friend as waiting forever. A restart in between lost it the same way.

Outgoing requests now live outgoingRequestTTL, which is the peer's
pendingHandshakeTTL (30 days), are saved in trust.json with the trusted,
pending and revoked state and restored on load (expired ones dropped),
and are bounded at maxOutgoingRequests (4096, oldest forgotten first).
Every removal goes through dropOutgoingLocked, which marks the state for
saving, so a cancelled, rejected or completed request does not come back
after a restart. The reaper test that encoded the old 10-minute rule now
checks the new boundary; new tests cover an acceptance hours later
(direct and relayed), a restart in between, a cancelled request across a
restart, and the bound.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codecov

codecov Bot commented Oct 8, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@TeoSlayer
TeoSlayer merged commit 616eab9 into main Oct 8, 2026
4 checks passed
@TeoSlayer
TeoSlayer deleted the fix/outgoing-request-lifetime branch October 8, 2026 11:13
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.

1 participant