fix(backend): retry + self-heal the cerastream engine connection#151
Merged
Conversation
Contributor
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
andrescera
force-pushed
the
fix/cerastream-connection-retry
branch
2 times, most recently
from
July 16, 2026 21:03
1ba25cb to
9f2867b
Compare
The engine capability contract was fetched over a short-lived probe exactly
once at boot (guardNonCritical("pipelines", initPipelines)). If cerastream
was not up yet — a systemd-ordering race or a slow engine start — the fallback
ladder marked the engine unavailable permanently: no retry, no recheck, so the
"Streaming engine offline" banner never cleared even after cerastream came up
healthy moments later. Confirmed on real hardware.
Add modules/streaming/engine-reconnect.ts (initEngineConnection), now the boot
"pipelines" init. It runs the first attempt synchronously (boot ordering
unchanged) then, if the engine is unreachable, arms one self-rescheduling loop:
a short exponential backoff (~2s,4s,8s,16s) that resolves a normal
engine-not-ready race, capping at a 30s ceiling as a bounded periodic
health-recheck so a device self-heals minutes/hours later. On the
unavailable->reachable transition it re-broadcasts capabilities/pipelines/
sources to connected clients (the same trio setMockHardware uses) so the
offline banner clears live without a reload, then settles.
The @ceralive/cerastream client's autoReconnect only rescues an already-live
connection and throws immediately on the first connect failure, so it cannot
cover the fresh per-fetch capability probe — recovery lives backend-side.
Reachability feeds the existing engine-unavailable/engine-starting tier; it
does not create a parallel state machine. Backoff mirrors the control-channel
convention; all collaborators are injected for testing.
andrescera
force-pushed
the
fix/cerastream-connection-retry
branch
from
July 16, 2026 23:17
9f2867b to
5370473
Compare
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.
What
Adds
modules/streaming/engine-reconnect.ts(initEngineConnection), which becomes the bootpipelinesinit inmain.ts. It runs the first engine capability probe synchronously (boot ordering unchanged), then — if cerastream is unreachable — arms one self-rescheduling reconnect loop:On the
unavailable → reachabletransition it re-broadcastscapabilities+pipelines+sourcesto already-connected clients (the same trio thesetMockHardwareRPC uses), so the "Streaming engine offline" banner clears live, without a page reload, then settles.Why
Confirmed on real hardware: the engine capability contract was fetched over a short-lived probe exactly once at boot inside
guardNonCritical("pipelines", initPipelines). Ifcerastream.servicewasn't up yet (a systemd-ordering race being fixed separately inimage-building-pipeline, or just a slow start), the fallback ladder marked the engine unavailable permanently — no retry, no recheck — so the offline banner never cleared even though cerastream came up healthy moments later. This is defense-in-depth: it stands on its own even with correct ordering, covering any transient failure (slow start, brief socket hiccup, a cerastream-only restart).The
@ceralive/cerastreamclient does exposeConnectOptions.autoReconnect, but it only rescues an already-established connection that later drops and throws immediately on the first connect failure — useless for the fresh per-fetch capability probe — and emits no "became available" event. So recovery lives backend-side. Reachability feeds the existingengine-unavailable/engine-startingcapability tier; no parallel state machine is introduced. Backoff mirrors themodules/remote-control/channel.tsconvention.Note: the separate version-skew error (
get-capabilitiesadditive method #9 missing on an older engine binary) is an already-tracked follow-up (redeploy a newer cerastream) and is not addressed here — the loop correctly treats it as "unavailable" and self-heals once a matching engine ships.How to verify
From
apps/backend/:bun test src/tests/engine-reconnect.test.ts— 8 tests: boot-retry heal, later out-of-band reconnect, backoff-ceiling cadence, and the permanently-unavailable case driven through the real capability ladder (proves no regression toengineUnavailable/engineStarting).bun tsc --noEmit— clean.biome check— clean.Full
bun testis green except 4 pre-existing environment/permissionEACCESfailures (/run/ceralive/srtla_ips,/var/run/bcrpt) unrelated to this change — none reference the new module or any file touched here.Risks
reconcilePersistedPipeline/sources steps). The loop is bounded (30s ceiling — a masked/disabled cerastream just gets a cheap periodic poll, never a tight loop) and settles once the engine is reachable. All collaborators are injected;stopEngineReconnect()/settleEngineReconnect()are the teardown/test seams. Docs updated inapps/backend/AGENTS.md.