Skip to content

20260923 - Offer the claim address again after a release - #22

Merged
Purple10101 merged 4 commits into
mainfrom
20260923-reclaim-after-release
Sep 23, 2026
Merged

Purple10101 merged 4 commits into
mainfrom
20260923-reclaim-after-release

Conversation

@Purple10101

@Purple10101 Purple10101 commented Sep 23, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes a node that could not be claimed again after its owner released it. (Unclaim & Reclaim Fail)

What happened on jonathan-node-1

Claimed at 13:04, owned at 13:05, released from the dashboard at 13:51. After that neither Save nor Send again on the retina-gui claim page produced a link.

A release clears the address on the server as well as the owner. A declined link does not: it leaves the address on file, which is the case #21 was built and verified around. The node heard about the release within a minute through the heartbeat (claim.email: null in its status document), but:

  • Save with the same address counted as no change and sent nothing.
  • Send again became POST /nodes/claim/resend, which the spec says mails "the address already on file". There was none, so nothing was mailed.

The fix

Send again on a node the server holds no address for is answered with PUT /nodes/claim rather than a resend. PUT is the call that mails. Where the server does hold an address (a declined link, a pending one) Send again is still a resend, exactly as before.

Nothing is sent on a release by itself. The owner may have released the node to hand it on, so mailing them a link to take it back unasked would be wrong. The address stays in retina-gui's file after a release and does not count as a change.

The mock gains release(), which does what production did on jn1.

Reading the commits

The branch took three goes, and the last one replaces most of the first two, so review the combined diff rather than commit by commit.

  1. 5402f85 re-offered the address as soon as the node saw a release.
  2. 0d11eae fixed a race in that, found by CI: the release could land before the node had noticed the address was held.
  3. 9d9f58a moves the decision to the owner's ask instead, so nothing is mailed unasked. This removes the tracking the first two added, and the race with it.
  4. A final commit updates the docstrings, data-sources.md and CLAUDE.md for retina-gui#96's single button, and records the release behaviour. Wording only.

Worth a look

  • Save with an unchanged address did nothing after a release, because retina-gui wrote an identical file for it and the node could not see the press. offworldlabs/retina-gui#96 removes Save and leaves one Send link button that stamps an ask on every press. Either PR can ship first; both are needed for the released-node case to work from the page.
  • An ask whose PUT fails is recorded as acted on, like a failed resend, so it is left for the owner to press again rather than repeated.

Verification

  • tools/check.sh and tools/check.sh --tracked pass.
  • test_a_released_node_is_left_alone_until_asked: after a release, no call for several heartbeats and ticks.
  • test_an_ask_on_a_released_node_offers_the_address: Send again after a release sends one PUT, and no resend. It fails without the change to __main__.py.
  • Both run 25 of 25 locally, having been bitten once by a race here.
  • Not yet run on a node. Until it ships, the workaround on jn1 is to clear the Email box, Save, wait about 30 seconds, then enter the address again and Save.

🤖 Generated with Claude Code

Purple10101 and others added 3 commits September 23, 2026 15:09
Reported on jonathan-node-1 (ClickUp 123zgec4mb1): claimed, released from
the dashboard, and then neither Save nor Send again produced a link.

A release clears the address on the server as well as the owner, unlike a
declined link, which keeps it. The node learnt of the release within a
minute through the heartbeat, but chose its call by comparing the file's
address with the one it last sent. That still matched, so Save counted as no
change and sent nothing, and Send again became a resend, which mails only
the address on file and so mailed nothing.

The node now remembers the address the server last said it holds. When that
goes from an address to null, the last offer is forgotten and the address
is offered again on the next tick. Only the transition resets it: an address
the server refused was never held, so it stays null throughout and is not
re-offered every tick for the same 400.

The mock gains release(), which does what production did on jn1.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The previous commit noticed a release by watching the server's address go
from set to null, but only sampled it at the start of each claim tick. A
release that reached the node before the tick after the PUT, by heartbeat,
therefore looked like an address that was never held, and the node stayed
silent. test_a_released_node_offers_its_address_again failed 23 runs in 40
locally and failed in CI on #22.

A 200 from PUT /nodes/claim is itself proof the server holds the address,
so it is recorded there. 60 runs in 60 now pass.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The first two commits re-offered the address as soon as the node noticed a
release, which mailed the previous owner a link to take the node back within
a minute of giving it up. That is wrong for an owner who released the node
to hand it on, and the ticket only ever needed the link to arrive when asked.

Nothing is sent on a release now. The address stays in retina-gui's file and
does not count as a change. The decision moves to the moment the owner
presses Send again: if the server holds no address, which is what a release
leaves, a resend would mail nothing, so the ask is answered with a PUT.
Otherwise it is a resend, as before.

This also retires the set-to-null tracking and the race the second commit
fixed in it, since nothing watches for the transition any more.

Save with an unchanged address still sends nothing after a release.
retina-gui writes an identical file for it, so the node cannot see it.
Send again, or a different address, are the two ways to ask.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
retina-gui#96 replaces Save and Send again with one Send link button, and
every press now writes the address and a fresh send_requested_at. The
docstrings, data-sources.md and CLAUDE.md still described the two buttons,
so they are brought up to date. No logic changes: the node already handles
an address with a stamp beside it, and an address with none, which an older
retina-gui or a hand edit still produces, stays valid.

data-sources.md and CLAUDE.md also gain the release behaviour this branch
was written for: a release clears the address where a decline keeps it, and
nothing is sent on a release by itself.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Purple10101
Purple10101 merged commit 993fe83 into main Sep 23, 2026
2 checks passed
@Purple10101
Purple10101 deleted the 20260923-reclaim-after-release branch September 23, 2026 15:24
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