From 1a55e8f205793ee15047d0dd4ab9866622264d86 Mon Sep 17 00:00:00 2001 From: jochen Date: Fri, 18 Sep 2026 02:06:53 +0200 Subject: [PATCH] Serve mode waits for its broker instead of crash-looping (issue 058) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit At startup 'the broker is not reachable yet' is the normal case — a container comes up in seconds, the overlay tunnel a moment later. Exiting delegated the retry to the container runtime, which read as a crash-loop to every restart-counting health check and every person watching docker ps. Serve mode now retries with capped backoff, aloud, indefinitely; a pinned-certificate mismatch still refuses at once, and one-shot commands (emit, invoke, run) still fail fast. --- src/main.ts | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/main.ts b/src/main.ts index c32805f..567ec8a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -66,13 +66,37 @@ async function connectBroker(): Promise { return connectAmqp(url); } +/** + * Connect for serve mode, retrying while the broker is merely not reachable yet. At startup that + * is the NORMAL case, not a failure: a container comes up in seconds and the overlay tunnel a + * moment later (novox/hq issue 058). Exiting instead delegated the retry to the container + * runtime, which read as a crash-loop to every restart-counting health check and every person + * watching. Retried indefinitely, aloud: the dependency appears or somebody reads why not. + * + * Only reachability retries. A pinned-certificate mismatch is a refusal, not a wait — an + * impostor does not become the broker by being asked again — and configuration errors already + * exit inside connectBroker before anything is thrown here. + */ +async function connectBrokerPatiently(): Promise { + for (let delay = 2_000; ; delay = Math.min(delay * 2, 30_000)) { + try { + return await connectBroker(); + } catch (err) { + const why = err instanceof Error ? err.message : String(err); + if (why.includes("does not match the pinned")) throw err; + console.error(`mesh-tools: the broker is not reachable yet (${why}); retrying in ${delay / 1000}s`); + await new Promise((r) => setTimeout(r, delay)); + } + } +} + async function serve(): Promise { const moduleEntrypoints = (process.env.MESH_TOOL_MODULES ?? "") .split(",") .map((s) => s.trim()) .filter(Boolean); - const broker = await connectBroker(); + const broker = await connectBrokerPatiently(); const stop = await runTools({ broker, moduleEntrypoints }); const shutdown = async (): Promise => { -- 2.54.0