shell: survive a box gateway that is not there (BUG-CV-01, BUG-CV-02) - #155
Merged
Conversation
A canary box booted with a cloudflared connector carrying zero ready connections. Every /webapp/7445/* call answered 530 for more than seven minutes. Two defects followed from that one dead tunnel. BUG-CV-01 (critical): the whole document went blank. Every box poll ran without a deadline, and the platform poller ran on a 500 ms setInterval that fired whether or not the previous read had answered. The browser ran out of sockets, the lazy SessionSurface import rejected with ERR_INSUFFICIENT_RESOURCES, and no boundary covered the rejection — so React unmounted the tree. Two halves are fixed: - SurfaceLoadBoundary catches the rejection and renders the degraded notice the surface already had, plus a Try again that builds a fresh lazy(). A failed chunk is now a message, never an empty body. - box-gateway-health.ts gives every box read a 10 s deadline, and the platform poller schedules its next read when the previous one lands. The terminal reconnect ceiling rises from 5 s to 30 s. BUG-CV-02 (major): the footer read "workspace running" throughout, because CloudApp rendered machine lifecycle state alone. Those same polls now report what they saw; three consecutive tunnel failures settle the shell on "unreachable", one answer clears it, and the footer reads "workspace running · box unreachable". No new poll and no new control-plane API: the evidence is the requests the shell already sends. Tests: packages/webapp/test/shell-gateway-resilience.test.tsx. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013voh4zgczP2jmPrS2ERrav
pythonlearner1025
added a commit
that referenced
this pull request
Sep 1, 2026
The redesign branched at #110, three hours before #112 landed `settings-surface.css` — the `cfg-` canon CLAUDE.md now names as the one system for every settings-shaped screen. Both sides restyled the same surfaces, so the resolution keeps the redesign's STRUCTURE and main's VOCABULARY: primitives, the org switcher, the surface ladder and the danger-zone/About headings survive, expressed in `cfg-` classes rather than in a second set of headings, fields, dividers and fact lists. Per file: - settings.css — the redesign's ladder and its `.settings-switch-*` shape stay; every rule whose markup moved to the canon is gone (definition list, appearance wrapper, `.settings-field*`, `.settings-danger*`, `.settings-note`, `.settings-credential-section`, `.connect-field__label`). The account page keeps its red frame around the canon's `.cfg-danger`. - workspace-details-dialog.css — the ladder's raised cards stay; the heading, field and fact-list rules the canon owns are gone, and #114's credential import and #112's volume meter come across whole. The settings form's machine-type select re-targets `.cfg-field` and takes the one control height. - SettingsPage.tsx — main's `.cfg-meta` profile facts and `.cfg-section` appearance block, plus the redesign's Organizations section, re-headed with `.cfg-section-head` / `.cfg-title`. - MembersPanel.tsx — the redesign's named "Danger zone" over main's `.cfg-danger` / `.cfg-danger-action` zone. - UsagePanel.tsx — the redesign's `SettingsSwitch` (it writes on change, which is what the primitive is for) with main's `.cfg-help` notes. - WorkspaceSettingsTab.tsx — main's `.cfg-field--inline` checkbox, NOT `SettingsSwitch`: that row is part of a draft the Save below sends, and the primitive's own contract forbids a Save beside it. `cfg-actions` and `cfg-meta` as main has them, with the redesign's "About" title. - WorkspaceDetailsDialog.tsx — main's `.cfg-section` credentials tab and its comment field, with the redesign's red Revoke. - CloudApp.tsx — the redesign cuts the desktop statusline; #155's BUG-CV-02 sentence keeps one desktop surface, because a box the browser cannot reach has no other affordance. The bar now shows on desktop when the terminal needs its sign-in pair OR when the gateway is unreachable. Also carried across the version gap: `settings-surface.css` gains the disabled state the redesign gave its danger button, the preview fixtures gain the fields main added to `MachineView`, `WorkspaceCredentialView` and `CatalogEntryView`, the preview gallery passes `refreshWorkspaces`, and two tests drop props the redesign removed. Gates: typecheck and lint:gate pass at baseline (74 anti-slop, 0 house). `npm test` passes except `webapp/test/lody-worktree-composer.test.tsx`, which fails the same way on origin/main alone — a pre-existing failure, not this merge's. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UhdxNAYjV97JTv2pddcJiN
pythonlearner1025
added a commit
that referenced
this pull request
Sep 2, 2026
…ing it — seam patch 15 (#173) * docs(lody): declare seam patch 15 — the host owns connectivity BlitzOS surfaces connectivity itself: the shell footer's left slot carries `workspace running · box unreachable` from `workspace-status-line.ts`, behind the gateway probe in `box-gateway-health.ts`. Lody, mounted inside that shell, tells the same story again in different words from a narrower vantage. This declares the patch that stops it, ahead of the edit, as CLAUDE.md requires: one optional prop, `hideConnectionStatus`, framed for any embedding host that reports connectivity itself. It names every surface it takes, every surface it deliberately leaves alone and why, and the five it found dark already. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UhdxNAYjV97JTv2pddcJiN * lody: apply seam patch 15 and gate every connection surface off v1-scope One optional prop, `hideConnectionStatus`, on the four levels that draw the connection: the status resolver, the chat surface, the session page and the file viewer, plus the chat landing for the mobile home's banner. Every hunk defaults to today's behaviour and no upstream call site passes it. Our side is a sixth flag in `v1-scope.ts`, `connectionStatus`, and two props in `router.tsx`. The flag's row says what the other five do not: this is an ownership boundary, not a "not in v1" cut. `shell/workspace-status-line.ts` already reports reachability for the whole workspace. `machine-removed` keeps its chip. It blocks sending, and the footer says nothing about it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UhdxNAYjV97JTv2pddcJiN * test(lody): pin every connection surface dark, and pin ours still lit Each cut is asserted both ways, with the underlying state ACTIVE in both: an offline browser and an offline machine resolve a chip for a host that reports nothing itself, and resolve null for the host BlitzOS is. The info bar renders "You are offline. Reconnect to sync." in the first case and not in the second; the catch-up spinner and the mobile pill get the same treatment. `machine-removed` is asserted to SURVIVE the suppression, because it blocks sending and the footer says nothing about it. The last block is PR #155 from this side: the probe settles on unreachable, the sentence changes, and `CloudApp.tsx` still renders it. Taking Lody's story away is only correct while ours is still told. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UhdxNAYjV97JTv2pddcJiN * docs(lody): cite seam patch 15's kept surfaces by key, not by line Two claims in the "leaves alone" list were wrong. There is no "Disconnected from sync server" string anywhere in the vendored tree, and `isOffline` is a field of `SessionListRow` that no renderer reads rather than something our rail computes. Both are corrected. The rest now cite i18n keys instead of line numbers. Every line number in that list belonged to a surface this patch does NOT touch, so nothing keeps them honest as the files move; the keys are what a merge agent can grep. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UhdxNAYjV97JTv2pddcJiN --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
pythonlearner1025
added a commit
that referenced
this pull request
Sep 2, 2026
…orm (#176) `the platform snapshot poller > never has two reads in flight against a box that does not answer` timed out at 5 s on main. The poller is not the cause. `platform.tsx` has not changed since #155 landed it, and the single-flight invariant holds: with a fetch that never answers, the hook issues one read and 30 s of fake time add none. The timed region was wrong. The test imported `../src/lody/platform.js` INSIDE its own `it()`, and that module pulls Convex and the vendored Lody providers behind it. Measured on a loaded box: 7 720 ms to import, 1 ms for the property. The 5 s timeout fired on module weight. The import is now static, so the transform lands in the file's import phase where no test timeout governs it. The assertion is unchanged: one read, thirty seconds later. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Canary workspace
zesty-swanbooted a box whose cloudflared connector carriedzero ready connections. Every
/workspaces/<ws>/webapp/7445/*call answered 530for more than seven minutes. Two defects followed from that one dead tunnel.
BUG-CV-01 (critical) — the whole webapp went blank
The shell painted for about five seconds, then
body.innerText.lengthwent 87to 0 on two loads out of two. The console held
Failed to fetch dynamically imported module: /assets/SessionSurface-*.jsafter a run ofERR_INSUFFICIENT_RESOURCES. The chunk itself was fine (200, 3 583 733 bytes).Cause: no box poll carried a deadline, and the platform poller ran on a 500 ms
setIntervalthat fired whether or not the previous read had answered. Thebrowser ran out of sockets, the lazy
SessionSurfaceimport rejected, and noerror boundary covered the rejection — so React unmounted the tree. The degraded
path already worked; nothing could reach it.
Both halves are fixed.
lody/SurfaceLoadBoundary.tsxcatches the rejection and renders the noticethe surface already had. A
Try againbutton builds a freshlazy(), becauseReact re-throws a rejected payload forever. A failed chunk is now a message,
never an empty body.
box-gateway-health.tsgives every box read a 10 s deadline through onehelper. The platform poller schedules its next read when the previous one
lands, and stretches to 30 s while the box is known unreachable. The terminal
reconnect ceiling rises from 5 s to 30 s.
BUG-CV-02 (major) — the footer said
workspace runningCloudApp.tsxrendered machine lifecycle state alone, so a machine that bootsbehind a dead connector reads as healthy.
Every box read now reports what it saw. Three consecutive tunnel failures (502,
504, 530, or a transport error) settle the shell on
unreachable; one answerclears it. A cold daemon's own 503 counts as reachable, because it came from the
box.
shell/workspace-status-line.tsfolds the signal in and the footer readsworkspace running · box unreachable.No new poll and no new control-plane API. The evidence is the requests the shell
already sends.
Tests
packages/webapp/test/shell-gateway-resilience.test.tsx(11 cases): a rejectedchunk renders the degraded notice and keeps the container non-empty; the retry
mounts the surface; every box read carries a deadline; three 530s change the
surfaced status to
workspace running · box unreachable; a cold 503 does not;the caller's own abort reports nothing; the platform poller makes one read in
thirty seconds against a box that never answers.
Gates
npm run typecheck— passnpm run lint:gate— pass (74 anti-slop, 0 blitz-house, unchanged)BLITZDEV_MANAGED=1 npm test— pass. Two real-daemon suites(
lody-sharing-relay,lody-worktree-session) failed on the first run andpassed on retry; they are the known daemon-harness flake.
🤖 Generated with Claude Code
https://claude.ai/code/session_013voh4zgczP2jmPrS2ERrav