Skip to content

Follow-ups to hosted storage opt-in: tools stop when a chat moves, closing sentences arrive, moved chats keep their words - #2536

Merged
AbirAbbas merged 18 commits into
devfrom
fix/relay-optin-followups
Nov 9, 2026
Merged

AbirAbbas merged 18 commits into
devfrom
fix/relay-optin-followups

Conversation

@AbirAbbas

@AbirAbbas AbirAbbas commented Nov 9, 2026 •

Copy link
Copy Markdown
Collaborator

Why

#2509 made hosted sync storage off until an account is approved. Testing it on two real computers (a Linux PC and a Mac) turned up the bugs below. Some come from #2509's storage-off roads, and some were already on dev. Each one has its own issue, with a replication anyone can run.

What changes

Storage-off chats

  • Fixes A storage-off chat keeps running tools after another computer continues it #2526: tools stop when the chat moves elsewhere.
    • Before, a turn already running kept running tools after another computer continued the chat, because with storage off no drive existed to check.
    • The tool gate now checks who holds a shared storage-off chat, without starting an uploading writer or renewing a hold. It follows the directory feed, trusts a reading for at most 15 s with the feed and 2 s without it, and lets queued tools share one reading, even a slow failed one.
    • Another computer's hold refuses the call with the same chat-moved sentence as the storage-on road. The window then stops admitting new saved turns until the person continues the chat.
    • This computer's own hold (live or lapsed), a free chat, a never-shared chat and an unreachable directory all keep working. A never-shared chat makes no request.
  • Fixes First /status after pairing misses the sync storage line on a non-hosted relay address #2528: pairing saves the storage answer at once, so an open window's first /status shows the sync storage line on a relay at a non-hosted address. The ask runs alongside the rest of pairing with its own budget of about 1.5 s. A timeout or a relay without the field changes nothing.

Closing a chat

  • Fixes What the last save says when a chat closes never reaches the window #2527: closing sentences reach the window.
    • A sentence said by a closing chat's final save, such as a displacement the final publish found, used to be dropped on the engine road, because the engine stopped forwarding sentences before that save ran.
    • Those sentences now come back on the closing receipt, in an optional savingNotices field that older builds ignore.
    • Where they appear: one chat closing notes them, app quit prints them once after the screen is restored (including a slow close that finishes after quit), and /new and /resume on a shared connection deliver them too.
    • The notice pump's delivery wait is cancellable, so a second window that stopped reading can no longer hold up the engine's close. A sentence delivered live in the last moment before quit, but not yet shown, is printed after the screen is restored, once.
  • Fixes Self-hosted relay answers 503 to an unauthenticated directory read after a chat is released #2532: no read after release, and a 401 for an unfinished sign-in. A closing drive no longer follows directory news after it has released its chat. The Go relay answers an authentication that cannot finish with 401, not 503 ("unreachable", which tells clients to retry). A real outage still answers 503.

Moving a chat

Local development

Not fixed here: #2531. A local wrangler dev relay stops serving accounts after about two. The issue now carries measurements: the memory admission check works as written, and idle members do release their share and recover. The fix is a design choice for the hosted relay: an admission that waits, or a local layout that spreads accounts across isolates. Production's coordinator namespace recorded no exceptions in the last seven days.

Manual. The manual says what a storage-off chat does when another computer continues it, where closing sentences appear, the copy label, the Git boundary, and pairing's storage ask.

Validation

🤖 Generated with Claude Code

AbirAbbas and others added 18 commits November 9, 2026 08:54
An already-open window on a relay at a non-hosted address showed no sync
storage line on its first /status after pairing, because nothing had asked the
relay yet and only the hosted addresses read as off before an answer. Each
successful pairing (code pairing, link approval, Home, /pair and grant
adoption) now asks once for the account's storage state and saves it beside
sync-url, so every window shows it at once. A relay without the field, or a
failed ask, leaves the saved answer as it was.

Fixes #2528

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On the engine road the window stopped receiving sentences before the closing
chat's final save ran, so a displacement the final publish discovered, or turns
it kept on a branch, was said to nobody. The notice pump is now joined before
the final publish, and the sentences that publish says come back in an optional
savingNotices field on the closing receipt. Closing one chat notes them in the
window, and app quit prints them once after the screen is restored, on the
engine road and with --no-host alike. An older peer ignores the field, and an
older engine sends none, so the two generations still talk.

Fixes #2527

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With storage off no drive runs, so the tool gate let every call through and a
turn already running kept executing commands after another computer had taken
the chat. A shared storage-off chat now checks who holds it at the tool gate,
following the directory feed the process already has and reading the chat
when its version changes, with one reading per chat at most every two seconds
when there is no live feed. Another computer's hold refuses the call with the
same chat-moved sentence as the storage-on road and leaves a viewer. This
computer's own hold, live or lapsed, a free chat, and an unreachable directory
all keep working, and a chat that was never shared makes no request.

Fixes #2526

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The hosted relay's build hook waits on /proc/pressure/io before compiling
core.wasm, and only Linux has that file, so npm run dev failed on a Mac before
the relay started. Where the file is missing the step now runs at once, and
where it exists the wait is unchanged; a script test in make test-tooling pins
all three cases. The README's local-run section names Node 22, the version CI
uses and the oldest wrangler accepts, and says what to do when the esbuild or
workerd install scripts did not run.

Fixes #2529

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The storage ask after pairing used the five-second directory budget and ran before the success sentence, so a stalled stats answer held pairing up. It now runs alongside the rest of the finishing work with its own budget of about a second and a half, and a timeout saves nothing and says nothing.

Refs #2528

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The busy case passed on any failure, so a missing timeout command or a gate that died at once looked like correct waiting. It now requires exit status 124.

Refs #2529

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A reading of who holds a storage-off chat stayed trusted for as long as the directory feed answered pings, so a missed announcement could leave tools running for good. It now expires after fifteen seconds with a live feed and two without one, and an announcement still refreshes it at once. A slow failed reading is shared with every tool queued behind it instead of being repeated by each. Once another computer holds the chat, the window also stops admitting new saved turns, as a storage-on viewer does, until the person continues it again.

Refs #2526

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ing sentence

Joining the notice pump before the final publish could wait forever on a second window that had stopped reading, so the engine never closed or saved. The pump's delivery wait is now cancellable, and a sentence it had not delivered stays queued for the closing receipt. A slow single close that finishes after the person quits now prints its sentence once after the screen is restored, and /new and /resume on a shared connection deliver the previous chat's final sentences with the swap. The manual page says what a storage-off chat does when another computer continues it and where closing sentences appear.

Refs #2527 #2526

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…shed sign-ins with 401

On the engine road a drive that had already released its chat still followed directory news while closing, so the self-hosted relay saw a read after release. When that read's authentication could not finish because the caller was leaving, the relay called it an outage and answered 503, which tells a client to retry. A closing drive now ignores ownership news once it has stopped, an authentication that cannot finish answers 401, and a live request facing a real coordination outage still answers 503.

Fixes #2532

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The native capture found ignore rules by searching upwards for a Git checkout without stopping at GIT_CEILING_DIRECTORIES, although the project's own Git commands stop there. An enclosing checkout beyond that boundary that ignored the chat's folder turned its saved words and files into an empty capture, and continuing it on another computer then failed on a missing .cell/meta.json. Capture now searches within the same boundary, so an enclosing checkout outside it cannot empty a chat, and a repository inside it keeps its ignore rules.

Fixes #2534

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A chat that arrives from another computer owns its working folder and still records where it came from, but the label checked ownership first and fell back to the plain owned-chat name, so --no-host showed project: codeaf. The recorded origin is now read first, so the label says <folder> (copy from another machine). The manual page also says what a closing chat stops doing and how a Git boundary affects moving a chat.

Fixes #2535

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The new sections about storage-off tools, closing sentences and the Git boundary used the words of that question so heavily that the manual's search no longer returned the page's opening section, the one that says the feature is available by default and how to pair first. Their headings and the closing text now name their own topic instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The storage ask that pairing overlaps with its remaining work ran in a bare goroutine, which the guarded-tree law forbids because a fault there could take the terminal surface down. It now runs through guard.Go and still closes its done channel, so the pairing that waits on it finishes either way.

Refs #2528

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The earlier boundary fix treated a GIT_CEILING_DIRECTORIES entry equal to the workspace as a fence, but Git only refuses to move up into a ceiling, so it still finds the enclosing repository and applies its ignore rules. In that layout a file the enclosing repository ignored could have entered saved history. Capture now asks git rev-parse --show-toplevel in the workspace with the same environment, so it agrees with Git for equal, ancestor, intervening and several ceilings, trailing slashes and symlinked paths. A Git failure keeps the ignore protection. A real seal-and-restore test proves an ignored confidential marker stays out in every one of those layouts and that a repository inside the boundary keeps its rules.

Refs #2534

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A sentence the engine delivered live in the last moments before quit was taken off its closing receipt, but the window was already past its last read, so it was neither shown nor printed. Live saving sentences now wait in their own queue, which both the window and the quit report read under one lock, so each is shown in the window or printed after the screen is restored, once. The manual and the change entry say this, and say that a workspace's own .furrowpolicy turning on gitignore still uses the engine's own discovery.

Refs #2527 #2534

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The test now holds the real release request, makes directory news from the other home and lets the feed announce it, and asserts that no ownership read follows, as well as the direct check after close.

Refs #2532

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@AbirAbbas
AbirAbbas merged commit 35d38c5 into dev Nov 9, 2026
9 checks passed
@AbirAbbas
AbirAbbas deleted the fix/relay-optin-followups branch November 9, 2026 17:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment