TETHERBAY / RELEASE 0.2.810

TetherBay 0.2.810
Get the update.

Get TetherBay 0.2.810 for your devices.

Download TetherBay

Choose a platform · Version 0.2.810

WHAT’S NEW

A quick look at this update

TetherBay 0.2.810 packages the latest documented beta improvements across relay resilience, network fallback, storage, and encrypted sync.

Full release notes

Highlights

  • W1 directory ACL source repair:

    Windows private-directory creation checks current ownership and the directory chain before applying a protected current-user DACL, omitting the redundant owner write. Unsafe existing directories remain refused. The original packaged Windows device-link failure remains open; its native cause and the required approval/import/restart and bidirectional file exchange remain unverified.

  • R4-IOS-MULTI (M2):

    The desktop composer can send a multi-file selection as individual encrypted transfers to own devices without batch support. Retried collections retain the original note, transfer IDs and accepted files, and pause while the current relay policy cannot accept the retained note.

  • R5-T3-B2-2:

    Correct clipboard image ownership lifetimes and native contact clipboard ownership markers.

  • R4-SND3-01:

    Pause batches on known relay quota refusals and allow explicit Resume with the original batch identity.

  • R5-T2-B1-3:

    Complete cancellation when a batch is known not to have dispatched, rather than leaving it stuck at Checking files.

  • R5-T3-B2-1:

    Restore the compare-code dialog's focus after the folder chooser closes, keeping an explicit match decision required.

  • R5-T2-B3-1:

    Reconcile missing encrypted chunks before a cold-resume upload and disclose when the upload must start over.

  • Donor restage recognizes the exact upload confirmed by a competing queue drain without dup

    Donor restage recognizes the exact upload confirmed by a competing queue drain without duplicating completion ticks or accepting a replaced operation or retired owner. Repository assembly metadata stays aligned with the current version.

Fixes

  • W1 directory ACL source repair: Windows private-directory creation checks current ownership and the directory chain before applying a protected current-user DACL, omitting the redundant owner write. Unsafe existing directories remain refused. The original packaged Windows device-link failure remains open; its native cause and the required approval/import/restart and bidirectional file exchange remain unverified.
  • R4-IOS-MULTI (M2): The desktop composer can send a multi-file selection as individual encrypted transfers to own devices without batch support. Retried collections retain the original note, transfer IDs and accepted files, and pause while the current relay policy cannot accept the retained note.
  • R5-T3-B2-2: Correct clipboard image ownership lifetimes and native contact clipboard ownership markers.
  • R4-SND3-01: Pause batches on known relay quota refusals and allow explicit Resume with the original batch identity.
  • R5-T2-B1-3: Complete cancellation when a batch is known not to have dispatched, rather than leaving it stuck at Checking files.
  • R5-T3-B2-1: Restore the compare-code dialog's focus after the folder chooser closes, keeping an explicit match decision required.
  • R5-T2-B3-1: Reconcile missing encrypted chunks before a cold-resume upload and disclose when the upload must start over.
  • Donor restage recognizes the exact upload confirmed by a competing queue drain without duplicating completion ticks or accepting a replaced operation or retired owner. Repository assembly metadata stays aligned with the current version.
  • Own-device clipboard sender notices stop when consent, the account or the device lifetime changes during delivery lookup.
  • Interrupted first-time iOS contact identity enrollment resumes with the same retained private keys. An unresolved clipboard apply no longer blocks the independent file identity path; the uncertain clipboard item remains paused.
  • Remove a duplicate Compose annotation introduced into the Android Contacts UI during branch integration.
  • iOS contact clipboard readback accepts byte-valued ownership markers and system-generated HTML representations while refusing foreign types, changed content and stale revisions. Selected desktop contacts display a single checkmark.
  • Synced folders switch on end-to-end encryption only when their devices are ready; uploads wait rather than falling back to unencrypted transfer.
  • Manual Send and new share links require end-to-end encryption. Clients that do not send the encryption fields receive HTTP 428; pending plaintext sends expire during migration.
  • Moving a registered device to another account requires proof from its current owner. Older clients receive HTTP 401 for this move until updated. Existing anonymous profiles whose stored session lacks a verified server address require a fresh sign-in before sending authenticated requests and cannot attach email to that device automatically.
  • Contact clipboard offers can carry pictures. Recipients need a client that understands the new picture offer format.
  • Formatted text copied from spreadsheets, browsers, email and documents keeps its formatting when shared.
  • File sends and public download links can carry a short comment.
  • The Send screen makes it easier to replace a file, change a recipient, find a destination and withdraw an offer.
  • Contact clipboard sharing and verification guidance are clearer, including a way to check again after verification is blocked.
  • Sync and relay work scale better with changed data as accounts grow; server operators can also select PostgreSQL metadata and Cloudflare R2 blob storage. Detailed behavior, compatibility limits and engineering changes follow below.
  • Moving an already registered device to a different account now requires proof from its current owner during email verification. Older clients that do not send this proof receive HTTP 401 for that move; an updated client can prove ownership when its stored session identifies the original server. This prevents an email code plus a known public device ID/key from taking over another account's device. Anonymous registration with an already registered public device ID also returns 401 instead of recovering that account from the public ID alone. A lost first response therefore requires a proven sign-in route, or a clearly separate new identity where no credential exists. Existing anonymous profiles whose on-disk session has no verified server address cannot provide prior-owner proof for email attachment, even after upgrading; their issuer cannot be inferred safely from editable launcher settings. Sessions without a verified issuer also stop sending bearer and refresh credentials until a fresh sign-in. The recovery flow and updated client behavior remain release qualification items.
  • Formatted text is shared as formatted text — it had been arriving flat. Copy from a spreadsheet, a browser, Gmail or Word, send it to a device or to a contact, and the formatting travels with it. Measured over fifteen clipboard flavours captured as the applications actually write them, eight sealed as formatted text before this change; all fifteen now do. The clipboard's HTML flavour is sanitized before it is sealed, and that sanitizer was refusing ordinary documents — silently, so the clip simply pasted as plain text — for several independent reasons: - A document's own wrapper was enough to lose everything. <html> was in none of the sanitizer's tag lists, and every real clipboard payload is a full document — CF_HTML on Windows, text/html on X11, public.html on macOS — so the renderer dropped the document. Formatted text had therefore never worked for any ordinary source, and no test covered a document. - Wrappers that mean nothing were treated as if they might. <section>, <header>, <nav>, <figure>, <font> and <caption> hide nothing and change nothing, but an unrecognised element is dropped with its content, which makes the renderer disagree with the text extractor and refuses the clip. They are now removed and their text kept — a list earned element by element, not a blanket unwrap. <template>, <dialog>, <details> and <noscript> hide their content, and <q> and <ruby> have a browser draw something the text does not contain, so those are still refused rather than quietly altered; <caption> and <strike> are kept as elements, because a strikethrough removed turns a cancelled booking into a confirmed one. - A single <hr> refused the whole clip. Void elements were pushed onto the open-element stack and never closed, so an <hr>, <input>, <col> or <wbr> — Outlook signatures, spreadsheet tables and Gmail messages carry them routinely — tripped the nesting check. - A spreadsheet copy lost its columns and a Gmail copy lost its lines. A newline in the source is whitespace, not a line break; LibreOffice Calc writes one <td> per line, so "Region⇥Revenue" arrived as two lines and stopped matching the plain text Calc had put on the clipboard beside it. In the other direction Gmail and Slack write one <div> per line with no <br> between them, and removing the wrapper removed the line it started. - A sealed clip its own recipient could not open. Sanitizing an already-sanitized document has to produce the identical document, and for one shape it did not, so Create sealed a bundle that Decode rejected. Because ContactClipboardEnvelope.Seal calls Decode itself, that was not a quiet downgrade — the send to the contact failed. - Ordinary markup was read as an attack. A < in head JavaScript, an apostrophe in an unquoted attribute, CDATA inside an <svg>, and the <!--> and --!> comment forms all ran the scan past the real close tag and refused clips that every browser renders. - Security, in the other direction. Four ways for content the source never displayed to reach the recipient were closed: /> no longer self-closes an element that is neither void nor foreign content, so <script/>alert(1), <iframe/> and a <canvas>'s fallback text stay dropped; a </head> written inside an attribute value or inside a <script> no longer ends the skip early; a nested <object/> is no longer miscounted so that the next </object> ends it; and a / inside an unquoted attribute value is no longer read as a self-close. Each was found by adversarial review of the fix *before* it merged, and each is pinned by a test that fails without it. - Known cost, accepted. A block whose line breaks come only from CSS (<div style="white-space:pre"> with raw newlines) is now refused rather than reflowed: style attributes are stripped, so the sanitizer cannot honour the rule that made those newlines visible, and reflowing them would change what the recipient reads. <pre> itself is unaffected.
  • A file you send can carry a comment — and for a while it could not. Sending a file, to a device or to a contact, now takes a short written note that travels with the transfer and renders on the recipient's row. The composer owns the draft's comment across a destination change, and the limit is 4096 UTF-16 units, enforced as a refusal *before* the upload with the real length stated — deliberately not as a UI MaxLength, because truncating a paste silently changes what the person thought they were sending. Behind a relay that does not advertise file-notes-v1 the comment box is withheld rather than offered and dropped. - Every comment was discarded on send, in the shipping client, for as long as the feature existed. FileSendCoordinator.SendAsync takes noteText as its seventh parameter and that parameter is optional; the one production call passed six arguments and stopped short of it. The compiler had nothing to say, the coordinator validated null every time, and the sender's own diagnostic line read comment=no for every send. The composer, the validator, the transport, the relay and the recipient's render controls were all correct and all defeated by one argument that was never passed. Neither side was told. A source-level guard now asserts that every call hands the comment over, because the seam has no unit test that could reach it.
  • A download link can carry a comment too. The public download page shows it above the download button, so a link sent on its own can say what it is.
  • A clipboard snapshot offered to a contact can now be a picture. Copy an image, send it to a contact, and they are offered the picture rather than a description of one (offer format 3, bounded at 8 MB canonical PNG). The bound is declared once in ClipboardBundle.MaxImageCanonicalBytes and derived everywhere that needs it — the offer header, the sealed envelope and the recipient's own read ceiling — with one deliberate exception: the server migration restates the number as a literal inside raw SQL, and a dedicated schema test pins the two together because restating a number in SQL is how the two would otherwise drift. - Flagged breaking because the gate is relay-side only. Sending a picture is withheld until the relay advertises contact-clipboard-images-v1, so older *relays* are safe. Nothing consults the recipient's client version: a client built before this window validates the offer header with if (Format is not 1 and not 2) throw new CryptographicException("Contact offer format is unsupported."), so a picture sent to an un-updated recipient fails on their side. Text offers to the same recipient are unaffected. This does not raise a version floor and issues no 426, so an old client stays connected and meets the failure only when it is first sent a picture — which makes shipping updated clients a precondition for the feature, not a follow-up to it. - Copying a spreadsheet range no longer sends a picture of the cells. Several applications put both a bitmap and the real tabular text on the clipboard; preferring the picture turned "send these cells" into a screenshot of them. The text now wins where it is the truer payload. - On Windows a copied picture arrived as the URL sitting next to it. A bitmap-only copy is no longer degraded into whatever text happened to share the clipboard. - A cleared image snapshot can no longer be published as a picture of zeroes, and "copy some text first" no longer stands in front of a clipboard that plainly holds an image.
  • The Send screen gained the five elements its approved design specified. The comment editor, the file card's Replace, the selected recipient's Change, the rail's collapsible counts, and the rail's pinned search and Create link were all in the approved v3 screens and none had been built — the screens landed outside the packet gates that would have noticed. A half-scrolled rail row no longer butts against the pinned Create link.
  • Recall was wired, and separately hidden, so it did neither. Two changes root-caused the same defect in opposite directions: one wired the Recall button to the relay, the other withheld the button because pressing it could only 404 and then tell the sender an offer had ended while it was still pending and still claimable. They merged without a textual conflict and were inert together — recall wired, button invisible, the sender still unable to recall a contact-clipboard offer. The guard's own precondition ("until recall is really wired") is now met, and Recall on a transfer that has already ended no longer offers itself.
  • A macOS keychain that refuses forever could park the clipboard forever. A keychain denial was treated as a permanent verdict, so one refusal disabled clipboard sync for the rest of the install's life with no way back. The refusal now expires.
  • A poisoned download retried forever. A payload that could never be applied was re-downloaded on every pass, indefinitely, consuming bandwidth on a file that could not succeed. The failure is now recorded so it stops, the record outlives nothing it depends on, and the one poison cause that previously reached no telemetry at all now raises a Bugsink issue.
  • Deleting a synced folder while the app was running never parked it — the agent put the root back. The folder was recreated underneath the user, and its row went on reporting a dead agent's cause. Removing a folder root now parks the folder, without overriding one the user had switched off deliberately here.
  • A report carrying integrity failures must name the cause, and its dialog must be actionable. An integrity failure previously arrived as a verdict with nothing to act on, and a permanently broken folder showed 0 rows under the Errors filter — the one filter a person opens to find out what is wrong.
  • "Waiting for a device" never said which device to turn on. It now names it. The deleted-files review no longer tells a deleted folder to start itself, the local-changes review no longer promises a list it cannot show, and the recovery bin names a missing root instead of showing an empty state that looks like success.
  • A contact was identified by their email on some surfaces and by a placeholder on the rest. One identity now renders the same way everywhere, including the case where there is no email to show. - Auditing that fix found nine defects in it, because it stored a rendered sentence in fields named *Email and every reader of those fields assumed an address: the Send rail's search filter inverted (typing an email surfaced only contacts *without* one), the live language switch stopped working, and HasEmail came to mean its opposite. The one that mattered most: the "Check contact" security dialog named the contact by display name alone, so a safety code could be compared against the wrong person's name. Full audit in docs/qa/contact-email-audit-20260917.md.
  • The transfer readout blinked, and a deferral erased it. Background housekeeping blanked the readout on every pass so the bar flickered; a deferral now stalls the readout rather than removing it, because a number that vanishes reads as a number that was lost.
  • The login supervisor's give-up reached telemetry but never the user. On Linux the start-at-login supervisor could give up silently; it now says so on screen as well as to Bugsink.
  • "Start over" and Delete account left the account's folder and clipboard encryption keys on the device. The teardown deleted folder-keys/ — which is the plaintext *handoff cache* the launcher writes for the agent it spawns, not the key store. The canonical secrets (folder-key.{folderId}, clipboard-key) live in the protected store under names that matched none of the globs the teardown swept, so after a user asked for their account to be removed, the real folder and clipboard keys survived on the machine — on Windows as readable .dpapi files. They are now taken with the account.
  • A locked keychain could mark a folder permanently unrecoverable. An OS key store that was unreachable for about 90 seconds (three retries) was misread as a bad envelope — a transient outage recorded as a terminal verdict about the data.
  • A file send outlived the account that started it. Only the file upload was left unbound to the account it began under; sign-out cancelled every other round trip and left it running. Its completion then ran under the *next* account: it wiped the file and destination that account had since chosen with no warning, toasted the previous account's filename and recipient onto their screen, and recorded the send against the wrong account. A stale Transfers refresh could likewise paint an Error banner about routes never called for the account now signed in.
  • "Copy Logs" copied nothing, silently, in the state every launch starts in. The button guarded on the virtualized row window rather than on the data it copies, and that window is deliberately left unmaintained while the log panel is collapsed, raw, or inactive — collapsed being the shipping default. No error, no clipboard write, nothing said.
  • Accept and Decline on a contact-clipboard offer wrote their failure message and then erased it. The next poll overwrote the line on the following tick, so a refused accept or decline showed the user nothing at all.
  • A clipboard send painted the shared send panel without taking ownership of it, so an unrelated file transfer's delivery-receipt poll — which runs every ~30s — could re-enable Send on top of an upload that was still in flight.
  • The Sync Check, Plans and Transfers screens were English in eight of nine languages. The drop-in catalog above is the small one; sweeping the embedded Core catalog (1,793 keys) for the same shape found 75 more shipping as raw English — including the whole Sync Check feature, every verdict sentence it exists to deliver. Russian was the only complete catalog. A pre-merge review of that fix caught one string it had itself left in English.
  • The launcher's drop-in override showed literal English to speakers of 8 of its 10 languages. The drop-in catalog wins over the embedded one key-by-key, so this was not a fallback — it was the string the reader actually got, which is why one session showed Home and Settings in Korean and Send in English. Seven languages (es, fr, it, ja, ko, pt-BR, zh-Hans) were missing 92 of 236 keys outright and stored English for 120-123 more; German had every key and still stored English in 49 of them. Send's next-step line was missing from the catalogs it needed. All filled in.
  • The first side of a new contact pair to open Send → Clipboard was told its own security data could not be read. Contact-clipboard key sets are published to the relay lazily, by each participant's own client, on that client's first security inspection — so whoever opens the surface first asks about a directory entry the peer has not created yet and gets 404 not_found. The client collapsed every failed lookup into one state, and the message for that state was the one written for a local vault failure: *"Security data could not be read. Transfer is paused without resetting saved trust."* Both halves were false — nothing had failed to read, and there was no saved trust. Send stayed blocked with no retry, and the security dialog kept its whole comparison block live over the text "Verification code unavailable", so the user could tick "I compared the entire code" and arm a "Codes match" button that was guaranteed to be refused. The relay's four different answers are now four different states: the peer has not set up clipboard sharing yet (name and remedy given), the relay could not be reached, the identity is rotating, and the security data genuinely could not be read. The dialog hides the code, the compare tick and both verdict buttons when there is no remote key to compare, every blocked notice carries a Check again control, and a blocked security state now reports itself with its error code instead of leaving no trace at all.
  • A clipboard sent to one of your own devices could fail permanently, and the app told you to keep retrying it. The relay retired an addressed clipboard event as soon as the recipient's fetch cursor moved past it, on the theory that a well-behaved client only advances that cursor after recording /clipboard/applied or /clipboard/skipped. A real send showed the cost: the receiver took the event, recorded neither, advanced its cursor, and the event became undeliverable forever — still applied=false skipped=false 75 minutes later, with the receiver online, clipboard-enabled, E2E-ready and holding the right key. Every retry minted another event that died identically. An addressed event is now retired by a disposition or by age, never by a cursor; a delivery window (120s, env-overridable) bounds the thing the cursor bound was protecting against — addressed events are served ahead of broadcasts, so one that is never retired would starve every later clip on that device. - The sender can now tell "never" from "not yet". delivery-status reports expired and deliverableUntilUtc; a closed window is terminal and offers no Retry, because retrying only mints a new event for the same recipient. Same defect class as the impossible Retry already removed from the account-E2E gate. - "Keep it pending and try again." is gone from all ten catalogs. It was false once the window closed. The pending message now describes the window instead of promising a retry, and a new terminal message says the send is over and where to look. - The failure reaches Bugsink. A targeted send that was never confirmed previously left no log line and no issue anywhere — the only record was a sentence on screen, which in the field nobody is reading.
  • Sharing a clipboard snapshot with a contact never worked at all. Every send, in both directions, for every account, had been refused by the relay since the feature shipped. The relay reads the offer's content multipart section by the length that section declares and answers 400 invalid_request for the whole request when the header is missing — and MultipartFormDataContent does not emit a per-part Content-Length on its own, so the client never sent one. The request was rejected before the relay read a byte of ciphertext. The client now declares the length it already knows. The test suite had stayed green throughout because the server-side test builds that multipart by hand and hand-sets the very header the shipping client omitted, so the parser was only ever exercised against a request no client produces; the new guard asserts on the bytes the shipping client hands the transport, which is the only place that drift is visible.
  • A clipboard send that could never succeed told you to try again, beside a Retry button it had already disabled. The relay marks every rejection retryable or not and the client carries the flag through — it was already withholding Retry correctly — but the message text keyed only on the error code, so any code the product does not name by hand fell through to "This operation could not be completed. Try again." That is how the permanent rejection above read to users, and to the QA run that chased it: a transient glitch worth retrying. Permanent failures now say plainly that trying again will not help, and a code the product does explain keeps its own wording either way. A source-level guard now enforces the rule across the send path rather than the single site that was fixed first — which is how the sibling two lines away was caught.
  • Closing the "Check contact" dialog accused the contact of a security mismatch they never reported. Dismissing the dialog — the Close button, or the window's own close, which leaves the result at its default — rendered the same "Do not send content. Confirm the contact through another trusted channel." text as an explicit *Codes do not match*. Dismissal now says verification was not completed. Sending stays blocked in both cases, as before; only the accusation is gone.
  • Contact Clipboard failures reached neither the log nor Bugsink. Three of the four ways a send could fail, and both ways the security check could fail, wrote nothing at all — no log line, no telemetry — so a failing send left no trace anywhere and the product could not say which of its own exits it had taken. Those paths now record the outcome and the error code and raise a counted issue, and the contact-security store funnels its unavailability causes through one reporting point instead of returning silently from 44 places.
  • Telemetry.CaptureError threw away the caller's message whenever an exception came with it. Only the exception was sent, so the sentence explaining what the code was trying to do never reached Bugsink. The message is now attached to the captured event; grouping is unchanged.
  • Retrying a clipboard publish quickly with the same event id could fail with a 500. On PostgreSQL the idempotency-replay path kept a reader open on the connection while committing on that same connection, which Npgsql refuses. The reader is now scoped so it closes before the transaction completes. SQLite tolerated the pattern, which is why the suite never saw it.
  • The production smoke test could return 9/9 green over a dead feature. Every check it ran was unauthenticated or a health probe, so nothing ever ran a metadata query as a real caller — which is how both Contact Clipboard offer-list routes answered 503 for *every* account on the service for days while smoke-production.sh reported an all-green stack. It now mints a throwaway anonymous session (the same /auth/anonymous call every launcher session makes) and requires 200 from /contact-clipboard/v1/offers/incoming, /offers/sent, the keyset page of the first, and /me/devices. A 503 from any of them fails the run and says why it must not be read as transient load. The keyset page is probed separately because the fault lived in the cursor predicate, where a first-page-only probe would miss a cursor-only variant. --traffic-token marks the canary session as qa rather than real traffic; --no-authenticated-reads opts out.
  • "Encrypted clipboard is not ready on every required device yet" was a dead end with a Retry button. Sending a clipboard snapshot to one of your own devices seals it under the account clipboard key, and the relay refuses to switch account clipboard encryption on unless *every* clipboard-enabled device can do it. One device that cannot — an Android build deliberately never advertises the capability — therefore keeps the whole account out of encrypted mode permanently, and every targeted clipboard send fails with that message. Retry was offered and could never succeed. The failure now names the device that is holding the account back and says what to do about it (turn Clipboard off for it under Devices, or update it — the relay's check counts only clipboard-enabled devices, so that alone lets the account flip), and Retry is withheld once a blocker can be named. Where no blocker can be named the same reason code may instead mean this device has not yet been handed the account key, which a later attempt does fix — so that case keeps both its old wording and its Retry.
  • Sharing your clipboard with a contact was impossible: the relay refused every request for the list of offers. Both Contact Clipboard offer-list routes (/contact-clipboard/v1/offers/incoming and /offers/sent) answered 503 temporarily_unavailable for every account, so an offer a contact sent you never appeared, your own sent offers never appeared, and Transfers could only report an incomplete list. The cause was one parameter. The paging query always carried its keyset cursor as a NULL placeholder (@cursor_ms IS NULL OR created_at_unix_ms<@cursor_ms); a NULL carries no type, so Postgres — which production runs — has to infer the parameter's type from the statement, strictly left to right, and a parameter whose first appearance is IS NULL is still untyped at that point. Postgres therefore rejected the whole statement with 42P18 could not determine data type of parameter $5 on every call, rows or no rows. SQLite is dynamically typed and never asked, so every test of this query was green. The predicate is now emitted only when there is a cursor to compare against, and the cursor parameters are bound with a stated type. Six other server queries that could bind an untyped NULL in the same position — the staged-blob ack audience that decides whether a blob may drain, legacy pending-membership expiry, restage candidate scanning and two Paddle paths — were repaired the same way, and a guard test now fails the build if any server SQL reintroduces the shape.
  • A permanent Contact Clipboard server fault no longer hides behind a "temporary" error. Any unhandled exception on a contact-clipboard route is converted to 503 temporarily_unavailable, retryable: true, and that conversion happened inside the error-tracking middleware — so the exception never reached it and no Bugsink issue was ever created. A hard, permanent fault could therefore tell every client to retry, every thirty seconds, for days, with no alarm anywhere; that is exactly how the outage above went unnoticed. These 503s are now captured as server errors.
  • Send and Transfers: seven places the app said something that was not true. Closing the findings the 2026-09-14 owed-rows run filed. - Recall now says what it did *not* take back. Recall is addressed to one transfer; the sentence it produced — "Withdrawn — the file is off the relay." — is about a *file*. Those are the same thing only while one live offer of that file to that person exists. A tester recalled one of two offers of the same file to the same contact, was told the file was off the relay, and the contact then downloaded the other one. A successful recall that leaves siblings now names them and points at the rows still to recall. - A refused recall is no longer silent. A 404/409 was swallowed on the reasoning that "the history refresh below shows what is actually true" — which the next item disproves. It now says the offer had already ended, and reaches Bugsink. - Every terminal outcome of a send repaints the Transfers list. None of the four terminal branches did, so after a decline, a withdrawal, an expiry or a delivery the sender's row kept saying *Waiting for Accept* with a live Recall button — indefinitely, until Refresh. - A failed upload reports the status the relay actually returned. An exhausted retry ladder hard-coded 503; a relay that refused five times with 429 was reported as "HTTP 503", so a support thread or a Bugsink issue matched nothing in the server's own logs. Retry is unaffected. - The Send destination rail no longer claims you have no devices or contacts when a filter emptied it — or when you have merely switched to Clipboard and none of your devices can take a clipboard yet. Four separate sentences and a Clear search way back, mirroring the Transfers list. - Leaving the Send page keeps your frozen snapshot, picked file and chosen destination. They were discarded silently — the same end state the page refuses to reach from the inside, where switching content rail with a snapshot held is declined out loud. - {0} no longer reaches the user. The Remote Access guide heading read literally "Opening a tunnel to {0}…", and a folder-removal toast would have read "'{0}' was removed from this device". A source-level rule now fails the build for any Loc.T on a format string. Five independent review passes over those fixes found nine further defects in them, all closed here. Three are user-visible in their own right: - Leaving the Send page during a clipboard send now tells you it was cancelled. Navigating away has always cancelled a running clipboard send; the cancellation was only ever painted onto the page you were leaving, so with the draft now preserved it would have looked like a send still in progress that could never finish. It raises a toast, and Send is usable again. - Signing out clears the Send composer. A frozen clipboard snapshot and a picked file belonged to the account you just left; nothing cleared them explicitly — the page reset did it by accident, and that accident is gone now that the draft survives navigation. - A recall no longer claims more than it can see. The confirmation says *this offer* is off the relay rather than *the file*, because the sender's history is scoped to the device it was sent from and a copy sent from another of your devices is invisible to it.
  • Linux: an unexpected launcher crash no longer stops every folder syncing until someone notices. Start-at-login on Linux wrote an autostart entry that launched the app exactly once per session. On 2026-09-14 the launcher died of an unhandled NullReferenceException inside Avalonia's X11 event dispatcher; because every per-folder TetherBay.Agent sync process is its child, all of them went with it, and nothing brought any of them back — no toast, no tray state, because the thing that would have shown it was the process that died. The autostart entry now runs a small generated per-user supervisor that relaunches the app after an unexpected exit, on a bounded backoff ladder, and stops without relaunching when the launcher records a deliberate Quit through the existing TETHERBAY_LAUNCHER_EXIT_MARKER protocol that macOS already uses. An entry written by an earlier build is detected as unsupervised and migrated on the next launch; a missing or tampered supervisor is reported as stale rather than as a healthy install. If the supervisor exhausts its restart budget it records why, and the next launch reports that outage instead of leaving it invisible. macOS (launchd KeepAlive) and Windows (installer-owned) startup are unchanged.
  • A clip sent to one device could stop that device receiving any clipboard at all. Addressed clips are served ahead of broadcast ones so that an earlier send cannot be skipped while it waits for its recipient. The query that does this dropped the cursor bound entirely, so an addressed clip the recipient neither applied nor dismissed was re-served on every poll and hid every later clip from that device until it aged out of the ten-event history. Clients in exactly that state are the norm rather than the exception: any build older than the dismissal route only advances its own cursor, and a phone dismiss reports nothing. A cursor already past the event now counts as the client's terminal answer. The desktop advances its cursor only after a disposition is recorded, so its retry loop is unchanged. A regression test covers the recipient that answers neither way.
  • A clip this device cannot decrypt no longer blocks the ones behind it forever. An event whose account key had not arrived held the cursor so a late key could still open it. Since addressed clips are served first, an event that would never open sat at the head of the queue indefinitely. The wait is kept but now ends after ten minutes, at which point the event is retired and the feed drains.
  • Contact Clipboard failures are now reported instead of swallowed. Every failure path in the feature was invisible once it left a developer's machine: the launcher logged breadcrumbs that never become issues and kept only the exception type name, and the server turned a database outage, a blob-storage failure or an unreadable stored grant into the same retryable error with nothing recorded anywhere. Infrastructure failures on both sides are now captured. Input validation stays quiet on purpose.
  • A share link that fails on your own machine no longer blames the server. When this device could not read or write the record that makes a link upload safe to retry, the app reported a server error code for a request it had never sent, and filed it alongside real relay outages. It now says that the link could not be prepared on this device, and is counted separately.
  • A share link's download limit now counts recipients, not fifteen-minute windows. Repeat requests from the same client inside a fifteen-minute window are deliberately not charged, so that a resumed browser download does not burn a slot. That exemption was keyed on the transport address — and every route is reverse-proxied to loopback in production, so *every* recipient of a link shared one bucket. After a single charged download, everyone else's download for the next fifteen minutes took the exempt path, which skips the limit check entirely and records nothing. A link offered "up to 10 times" could be fetched by any number of people while its count moved at most once per window. The grant is now keyed on the resolved caller — the rightmost X-Forwarded-For entry, the one our own proxy appends — which is what the rate limiter has always used and documents. Also applies to the view counter, which collapsed the same way.
  • Blocking from an offer says the contact was dropped even when the file could not be removed. The half-landed case — block succeeded, declining the file did not — kept its own honest sentence about the file still being listed, but never mentioned that the person had left your contacts. It was missed when the offer door was first covered; the wording test now enumerates every toast raised after a successful block rather than naming them one at a time.
  • The share-link footnote now describes the limit the product actually applies. It said "The file can be downloaded up to 10 times." What is capped is ten *grants*, where repeat downloads from the same network inside fifteen minutes count as one — deliberately, so that a browser resuming an interrupted transfer does not burn a slot (a single one-kilobyte range request used to consume two of the ten). The consequence is that one recipient, or a whole office behind one gateway, can download the file any number of times against a single count; a sender reading the old sentence as "ten copies leave my hands and then it stops" did not get that. The sentence now says so, and because it quotes the fifteen-minute window, a test ties it to the server constant so the two cannot drift apart.
  • Blocking someone from an incoming file offer now says they also left your contacts. Block has two doors — a contact's card, and Block on an incoming offer in Transfers — and both call the same thing, so both drop the person from your contacts as well as stopping them reaching you. Only the first one said so. The offer version reported just "{0} is blocked and the file was deleted. They can't send you anything new.", so a contact quietly disappeared with nothing to explain it. Blocking deliberately has no confirmation step — it is the protective action and must stay the fastest control there — which makes that one sentence the only account of it anyone gets. It now discloses the removal too, and the test that has guarded the wording since the contacts-card version of this bug was fixed now covers every door instead of one.
  • A contact you were once refused can become sendable again without restarting. When the relay permanently refuses a send to a contact — they removed you, or blocked you — the picker stops offering that person, so the next attempt is not walked into the same dead end. That mark was only ever lifted by a send that landed, and while it stood the contact could not be selected to send to, so the one event that cleared it was the one event it prevented. Relationships get repaired: the person accepts you again, the block comes off, and the relay reports both directions active while the sender's screen still refuses them. Until now the only way out was quitting the app, which nothing on screen suggested, even though the row itself says "right now". The refusal now expires after five minutes — a landed send still clears it at once — and, because the row's enabled state was being written onto list items that a contacts refresh rebuilt a moment later, it could also flip back to clickable while still captioned "They can't receive files from you right now"; the caption and the button now come from the same place and cannot disagree.
  • Send no longer stays lit on a file you were just told it refused. Choose a file, then choose one the app rejects — too large, a folder, something that has since moved. The rejection sentence appears and the file card empties, so the screen says no file is chosen. The Send button underneath it stayed enabled anyway, on the file accepted a moment earlier and no longer visible anywhere: the rejection path cleared the view's own state but never told the draft, so its "can I submit?" answer was still yes. Every other path that drops a selection already cleared both; this one is now consistent with them, and the rejection branch is reachable from a test for the first time.
  • Transfers stops claiming a source is unavailable when none is. With clipboard sharing off — the shipped default — GET /clipboard/recent correctly answers 204 No Content, and the client parsed that empty body anyway, recorded the source as failed, and left the Transfers screen permanently showing the red "Transfers partially refreshed · Some sources were unavailable" banner with a Retry that could never succeed. Nothing was wrong, and the consequence was worse than the false alarm: the empty-state card is gated on a fully successful load, so a brand-new user could never see "No transfers yet" at all. A 204 is now read as an empty source that succeeded. While the path was open, a genuinely failed source also began reporting to telemetry instead of only to launcher-user.log, which is a no-op in shipped builds — so this failure was previously invisible in the field even as the user was being told about it.
  • Folders say which way they sync again. The IN/OUT badge that tells you whether a folder's files arrive on this device or leave it had been removed from the Folders page and the Home folder list, leaving "Owner — you" as the only hint — which answers a different question, since a folder you own can still have arrived from another of your own devices. It is back on all three row layouts, and the contract test that was meant to protect it (and had been inverted into a test that the badge must be *absent*) now pins it in both the wide and the collapsed layout.
  • Phones and tablets can reach their own folders again. A relay change meant to fix which devices count as sync peers was applied to the wrong gate, and every companion device — which browses and fetches single files but never keeps a full copy — was refused the file list and every download on every folder. A collaborator whose device had no explicit per-folder setting stopped syncing for the same reason. Only a folder you have explicitly switched off for a device blocks it now, which is what it always said it did.
  • A folder can no longer report itself finished when nothing was sent. When the relay refused an upload the app was retrying, the transfer still counted the file as delivered, so the progress bar walked to 100% and the folder settled as done while the file was still waiting. The file is now left counted as pending — which is what it is.
  • "Clean up" asks again if you leave it. Permanently deleting a recovered file takes two clicks, but the armed state never expired: come back later and the click you read as your first destroyed the file. The confirmation now lapses after ten seconds and the button says so. Changing language while a row was armed also reset the button to "Clean up" without disarming it, so the very next click deleted with no confirmation on screen.
  • Reviewing a very long list of local removals no longer overpromises. Lists above 5,000 entries are shown a page at a time, but acknowledging one page claimed the folder would stop reporting them shortly. It now tells you how many of the total you have reviewed and that reopening the list gets you the rest.
  • An unreadable settings file no longer costs you your setup. The app has always kept a copy of the previous settings beside the live one, but never read it — so a corrupted file fell through to defaults, and the next save wrote those defaults over your device identity, server address and folder list. The copy is now used to recover them.
  • One bad timestamp no longer hides a folder's whole history. A single unreadable date in stored activity made the folder's file history fail to load entirely instead of showing the other events.
  • Two links now describe themselves accurately. "Link a device" said the link "can add up to 10 devices" — it adds exactly one, and the server refuses the second attempt with "This link was already used". That 10 was the *email sign-in* link's budget, quoted under the wrong credential; the join screen had been telling you the truth all along. Separately, the Send page called a download link "one-time" when it allows ten downloads, which the dialog and the link counter both already showed. A person setting up a third device no longer follows the app's own advice into a dead end, and nobody assumes a shared download link is already spent.
  • Blocking someone now says what it actually does. The confirmation promised "They can't send you files", but a file they had already offered stays in your Transfers list — blocking stops new sends, it does not retract an offer already made. It now says "anything new". The leftover offer costs you nothing: it is held against the *sender's* relay quota, never yours, and nothing reaches your disk unless you accept it.
  • Blocking someone now leaves a record of itself. The relay had a column for when a block began and never wrote it, so the one control a person has over who may send them files kept no audit trail at all. It is stamped when the block starts and cleared when the block is lifted, so it always answers "since when is this in force" rather than "was this person ever blocked". Nothing reads it yet and no behaviour changes.
  • A file that fails to send can no longer be repainted as sent. While an earlier file was still waiting for someone to accept it, that older transfer kept checking in — and when a newer send was refused, the check-in overwrote the red failure with a green "Sent … 100%" describing the *older* file. The refusal raises no other notice, so that stomped line was the only sign anything had gone wrong; worse, the false success also cleared the picker, silently discarding the file that had been armed to retry. A check-in now updates the send panel only while the panel is still describing its own transfer.
  • A folder never claims to be encrypted with a key this device could not make. If the keyring refused at the moment a folder was created, the app turned folder encryption on anyway — and then had no key to encrypt with, so it quietly held every upload back while the folder read as encrypted and up to date. Nothing could undo it afterwards. The app now leaves encryption off when it cannot mint the key, keeps syncing, and reports the failure.
  • Blocking someone no longer erases their name from what you already exchanged. Transfer history took its labels from the list of people you may send to, so blocking somebody rewrote every past transfer with them into a raw account id like "user-1a2b3c4d". Blocking stops new sends; it no longer rewrites the record of old ones.
  • The admin contact route stops inventing contacts. Adding a contact the owner had blocked answered "200 OK" with a made-up id that pointed at no record — and raised a false internal alarm about database corruption on a path that was working exactly as designed. It now answers 409 and says the account is blocked.
  • Blocking a contact now actually blocks them. The button was there and the relay had enforced the blocked state for weeks, but the app asked for it the wrong way and the request was refused every single time — with a message suggesting you try again, which could never work. Blocking works now, and if a relay ever does refuse it the app says so plainly instead of inviting you to retry.
  • A file taken on one of your devices stops nagging the others. When someone sent you a file and you accepted it on your laptop, your other devices kept a "Transfers 1" badge pointing at an empty page until the app was restarted. Any device now notices the file is gone, whichever screen it happens to be showing.
  • Inviting a person no longer reports that a device joined. The window that hands over a folder invitation announced "Device joined!" when the person accepted — the same wording used when you add one of your own devices, which is exactly the event you are told to watch out for. It now names the folder they joined.
  • Removing a contact tells you about a file still on its way to them. Removing someone does not recall a file they have not picked up yet — they can still accept it. The confirmation now says so, and points at Withdraw.
  • A failed send no longer looks like a successful one. The progress bar was green whatever it was reporting, including directly under the word "Failed". Sending, delivered and failed now each have their own colour. If someone has made themselves unreachable to you, the picker remembers the refusal you were already shown rather than letting you walk into it again.
  • Sending a file no longer leaves it armed. The file you just sent stayed selected with Send still enabled, so returning to the page put a duplicate one click away — a duplicate that now lands in another person's transfers, not just on your own laptop.
  • Live QA evidence now fails closed when candidate identities drift. The evidence validator checks the manifest, coverage matrix, verdict, packaged artifact hashes, screenshots, and every app instance claimed by a full live pass. A three-client result can no longer be attached to a later two-client delta build without the mismatch being reported, and the evidence contract is now exercised by the main CI workflow.
  • The README no longer promises a notarized Mac build when the available beta is ad-hoc signed. Its build badge now reflects the real cross-platform workflow, its version is current, and the Mac download row directs readers to the release-specific signing and first-launch status.
  • After you're given Editor access, saving the file again really does share it. A file you changed while you only had read-only access is kept local for safety, and the app tells you to "edit, save, or rename each file again to share it". Renaming worked; saving did nothing at all, however many times you tried, because most editors save by writing a new file and moving it into place. Saving now publishes it. A file you have not touched since is still kept back — being given access does not push out an old edit you may not want sent.
  • You can now take back a file you sent to a contact. Transfer history offered Open and Reveal, both of which act on your own copy — there was no way to recall a file sent to the wrong person, so it sat readable on our servers until it expired. Sent offers that nobody has picked up yet now have a Withdraw button, and using it removes the file from the relay.
  • You can now block a contact. Removing a contact was never a block — the app said so itself, since the person can simply add you again — and there was nothing else to reach for. Blocking someone stops them sending you files and stops them sharing folders with you. Blocked people stay listed, marked as blocked, so you can undo it.
  • Invitations you emailed are visible again — and can be revoked. An invitation sent to someone without a TetherBay account appeared nowhere: not in Contacts, not on the folder, not in Manage access, which is where the app itself tells you to go to revoke one. A mistyped address could not be undone for the invitation's whole week. Manage access now lists outstanding invitations for folders you own, each with the same ✕ as anyone else.
  • Sending to a contact appears within minutes of the relay offering it. The app asked once and remembered a "no" for as long as it stayed open, so a desktop left running when the feature was switched on kept the option hidden — and, on the receiving side, never picked up files people had sent. A "no" is now re-checked periodically.
  • Your transfer list now says what became of each file you sent. Delivered, declined, withdrawn, waiting and expired all used to read the same word — "sent" — which was nearly true when the only destination was your own device, and stopped being true the moment you could send to a person. A file someone refused looked exactly like one that arrived.
  • Taking a file back no longer tells the other person it "expired". Withdrawing an offer was recorded as an expiry, so someone pressing Accept a moment too late was told they had been too slow, when in fact the sender had changed their mind. The two are now different events with different words.
  • Transfers use the name you gave a contact. A row could show a raw account id for somebody your Contacts page names perfectly well, because it used the name that person set for themselves rather than the one you chose for them.
  • Send-to-a-contact no longer disappears until you restart the app. If the relay ever answered "not available" — an older relay, or the feature switched off at that moment — the app remembered that answer for the rest of the session, even after you pointed it at a relay that does offer it. The answer is now remembered per relay.
  • An invitation emailed to an address can no longer get stuck retrying. When the folder behind it had lost its owner, the claim re-ran on every contacts poll for the invitation's whole lifetime and re-added both people to each other's contacts each time. Nothing is written now until the claim can actually succeed, and a claim that has to wait backs off instead of retrying every 30 seconds.
  • Re-adding a contact can no longer quietly undo a block.
  • You can now send a file to a contact, not just to your own devices. Pick a file, pick a person, send. Their app shows it as an offer waiting for a decision, and nothing lands on their disk until they accept — one of their devices then downloads it into its Received folder. You can withdraw an offer until it is claimed, and you always see what happened: waiting, accepted, declined, or expired. Files sent this way are encrypted in transit and at rest but are not end-to-end encrypted — the Send screen and the encryption page both say so; put it in a synced folder if you need end-to-end. Neither side ever learns anything about the other's devices. The relay operator enables this per deployment.
  • The encryption page now lists one-off Sends. Its "what is and isn't end-to-end" table covered synced folders, clipboard and browser links but silently omitted device-to-device Send, which has never been end-to-end.
  • Accepting a shared folder no longer starts with a trip through your file system. Pressing Accept opened a folder picker immediately, so a folder someone shared with you could not arrive until you went and found — or created — a location on your own disk. It now proposes your incoming folder (TetherBay Incoming/<folder name>), shows you the exact path before anything is downloaded, and keeps the picker behind "Change location…" for the times you want it somewhere else. "Not now" leaves the invitation waiting in Contacts, and backing out of the picker returns to the choice instead of deciding for you.
  • A folder shared without a link can now actually be read. An encrypted folder's key only ever travelled inside an invite link, so anyone brought in another way — shared with as a contact, or claiming an invitation sent to their email — became a full member of a folder they could not decrypt: every file failed its integrity check, silently, with nothing shown to either side. The owner's app is now told when a member is waiting for the key and seals a copy for them, which the member's app collects on its own. Only the owner can do this, only for people who have already accepted, and the relay still cannot read any of it.
  • A folder invitation sent to someone's email now finds them after they install the app. Sharing with a person who has no TetherBay account yet mails them a link, and until now that link was the only way in: someone who installed TetherBay and signed in with the very address the invitation was sent to saw no invitation anywhere, because none existed for their account to report. Signing in with an invited address now claims the invitation and shows it with Accept/Decline, and an invitation that arrives later reaches an already-signed-in recipient on the next refresh. It still grants nothing until they accept. Device-link invitations are deliberately excluded, and only an address the account has verified counts.
  • The invitation email and the join page now say what to do after installing. Both previously assumed the reader already had the app — "open the invite", or "paste this link" — advice that does not survive an install, since the installer does not carry the link the recipient arrived from. Both now name the address to sign in with.
  • Windows Setup no longer aborts an install over legacy-updater cleanup. Setup still stops and removes the obsolete auto-updater, but a cleanup step it cannot verify through WMI or Task Scheduler is now logged and the installation continues, instead of ending in a restart-and-retry message a clean machine could not act on.
  • Beta upgrades now reach the permanent Paddle Sandbox checkout. The production relay and desktop client accept the official tetherbay.com pricing handoff when the server explicitly reports Sandbox mode, while temporary tunnel URLs remain restricted to loopback QA relays. Beta testers keep real email-backed accounts and durable entitlements; only the Paddle transaction uses test money.
  • Fresh Windows installs no longer fail a legacy-updater check. Setup now treats an absent legacy scheduled task as the normal state without depending on a version-specific Task Scheduler error identifier. A detected enabled legacy task or a still-running updater continues to block installation.
  • Windows upgrades no longer fail on harmless legacy cleanup leftovers. Setup checks only the named legacy scheduled task instead of enumerating every task on the machine, accepts a task that is already disabled, and treats a locked obsolete LocalAppData application tree as recoverable after proving that no legacy updater is running. An active updater or enabled legacy task still blocks the upgrade.
  • RecruitLead release artifacts remain readable after atomic activation. The mirror publisher now normalizes every staged download to mode 0644 before moving it into Caddy's document root, so a restrictive SSH umask cannot turn a hash-verified release into public HTTP 403 responses.
  • Windows setup now retries transient legacy-tree file locks. After stopping the obsolete updater, both Setup and the one-line installer retry removal of its LocalAppData application tree for up to 15 seconds. A short-lived archive or process handle can no longer turn a successful updater shutdown into an immediate restart-required installation failure.
  • RecruitLead download instructions no longer depend on the primary domain. Pages reached through the CIS mirror now show mirror-hosted one-line installer URLs, and /get.ps1 plus /get.sh default their release-feed requests to that same mirror ingress and use the mirror artifact URL returned by the feed. Mirror deployment and release verification reject either script if it contains a primary-domain dependency.
  • Windows upgrades now retire even an already-running legacy beta updater. Setup disables and removes the old scheduled task, terminates its active PowerShell/VBS process tree, verifies the scheduled task is absent or disabled, and makes a best-effort removal of the obsolete LocalAppData application copy. This closes the race that could restore and launch an old beta one second after a successful Program Files installation while preserving settings and synced folders.
  • Windows one-line upgrades now complete cleanly over running beta clients. The PowerShell flow downloads a recognisably named installer, closes existing launcher and agent processes, waits for setup to finish, verifies the installed binary version, and explicitly opens the matching launcher from Program Files. The release packager also rejects stale launcher or agent payloads whose FileVersion does not match the installer version, so a current filename cannot conceal old application bytes.
  • Sync server no longer slows down as the total number of synced files grows. The relay's metadata store became database-authoritative: per-write work is now proportional to what actually changed (indexed point/range queries) instead of the whole dataset, and the server no longer loads all records into memory at startup — so boot time and memory stay flat as the service grows.
  • One busy account can no longer degrade everyone else. Write contention is now keyed per folder/account behind a new lock manager (opt-in sharded mode), with a fair-share soft throttle that *slows* an over-active tenant rather than blocking it, and per-tenant lock metrics on /metrics. Closes the single-account denial-of-service path that could previously wedge the whole relay.
  • PostgreSQL is now a supported metadata backend for the relay (selectable by configuration), alongside the existing SQLite store which remains the default for local and self-hosted use. Includes a one-shot SQLite→Postgres data migration tool with row-count reconciliation.
  • Cloudflare R2 (S3-compatible) blob storage is production-ready. The relay can store encrypted blobs in R2 with presigned zero-egress downloads, a live storage health/deep-probe check, and a global storage-cap safety valve; switching is a configuration change. Verified end-to-end against a real S3 server.
  • Server startup no longer waits on the boot-time storage maintenance pass. The audit that repairs/cleans staged and orphaned blobs now runs after the server is listening instead of before it binds, so health checks come up immediately even on an object-store backend where that pass can take minutes. Controlled by TETHERBAY_STARTUP_AUDIT_MODE (background default, off to skip at boot, blocking for the legacy pre-bind behavior).
  • Folder encryption now actually takes effect. The feature below shipped complete and never switched on: a device recorded its encryption key but was never marked as *able* to use it, so the check "do all devices in this folder support encryption?" could never pass, no folder was ever turned on, and file contents went to the relay in the clear — while the app said synced folders were end-to-end encrypted. Devices now declare that capability, and a folder you own turns encryption on as soon as every device in it can handle it, rather than only at the moment the folder is created. Same fail-closed rollout as below: a folder holds uploads rather than sending anything in the clear, and a folder is never turned on unless the device turning it on holds the key — so a folder that is already syncing cannot be stopped by enabling it.
  • End-to-end encryption for synced folders and clipboard. File contents are encrypted on the sending device with AES-256-GCM (framed streaming AEAD) before upload; folder keys are shared device-to-device via sealed ECDH envelopes and never reach the relay. Clipboard items are sealed per-event with the account key. The relay stores ciphertext only and has no decryption path. Rollout is fail-closed — uploads are held rather than sent in the clear if any device in a folder cannot yet support E2E, so every device in a folder must be updated before content flows again. Clipboard E2E defaults ON since 2026-06-15.
  • Remote file access hardened alongside folder encryption.
Known issues (3)

Known issues

  • This beta includes completed changes whose full cross-device product QA is still incomplete.
  • macOS beta packages are ad-hoc signed and are not notarized; follow the published first-launch instructions.
  • The direct Windows beta installer is unsigned and may show an Unknown Publisher or SmartScreen warning.
Checksums and verification
  • Windows38df00027d2126d9c35583a71c7d80ac3a78cb130dcab49825fc9fb3b394bfef
  • macOS · Apple Silicon0fc2f43f01231efba4a0c4abf2b4f7fd5a25dd669431f382a3d6b3b958ab2b72
  • macOS · Intel9111b0ac0290af2020e3c4b903c8ec6e80a32323283152abaa2444c8ce400041
  • Linux · x6472e61f0353827200bebcf28ea381e4c894cd81e7dff9b7d1d850d58eb1c0ba5d
  • Linux · ARM647dc69b0a30d9bdd9b1e63b65f7599a18afb7d779bcf5d4e5eb2cbe965ab517a9
  • Androidd0a7090f0f807032e6cb519ffe490395d61925a1d7dce8a66a388d7833548003

GET THE UPDATE

Update your other devices.

Download TetherBay 0.2.810 for each of your devices.

Download TetherBay

Choose a platform · Version 0.2.810