From 74b3e450b17df9054e2931a2e20094e671a0f593 Mon Sep 17 00:00:00 2001 From: Prasenjit Sarkar Date: Tue, 7 Jul 2026 22:12:11 +0100 Subject: [PATCH] fix(desktop): first-launch resilience - Raise the sidecar boot deadline from 30s to 90s: on the first launch Gatekeeper verifies the whole ~400MB bundle, which can exceed 30s on slower machines and produced a spurious 'server did not start' dialog - Update the splash after 15s to explain that the first launch takes longer while macOS verifies the app - Include the recent sidecar log tail in the startup-failure dialog so error reports are actionable - Log when a second instance defers to the one already running --- CHANGELOG.md | 6 ++++++ desktop/main/index.js | 13 +++++++++++++ desktop/main/server-manager.js | 20 ++++++++++++++++++-- desktop/package.json | 2 +- web/package.json | 2 +- 5 files changed, 39 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f39248..2482e61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ > Campfire began as a fork of [the-companion](https://github.com/The-Vibe-Company/companion) and diverged into a separate product. Pre-fork history (versions up to 0.42.0) lives in the upstream repository; Campfire's own releases start at 0.1.0. +## 0.4.1 (2026-07-07) + +### Fixes + +* **desktop:** first-launch resilience — the server boot deadline is now 90 s (Gatekeeper verifies the whole ~400 MB bundle on first launch, which can exceed the old 30 s limit on slower machines), the splash explains the wait after 15 s instead of looking hung, the startup-failure dialog includes the recent server log tail, and launching a second instance logs that it is focusing the existing one + ## 0.4.0 (2026-07-07) ### Features diff --git a/desktop/main/index.js b/desktop/main/index.js index 73d9939..54187d2 100644 --- a/desktop/main/index.js +++ b/desktop/main/index.js @@ -28,6 +28,7 @@ let quitting = false; // ── Single instance ────────────────────────────────────────────────────────── if (!app.requestSingleInstanceLock()) { + console.log("[desktop] Another Campfire instance is already running — focusing it instead."); app.exit(0); } app.on("second-instance", () => { @@ -109,10 +110,21 @@ async function bootAndLoad(win) { win.once("ready-to-show", () => win.show()); } + // First launches can sit in Gatekeeper verification for a while — tell the + // user the wait is expected instead of looking hung. + const slowBootNote = setTimeout(() => { + win.webContents + .executeJavaScript( + `document.getElementById("s").textContent = "Still starting — the first launch can take a minute while macOS verifies the app…"`, + ) + .catch(() => { /* window may have navigated */ }); + }, 15000); + let result; try { result = await serverManager.ensure(); } catch (err) { + clearTimeout(slowBootNote); if (SMOKE) { console.error(`SMOKE_FAIL: ${err.message}`); app.exit(1); @@ -128,6 +140,7 @@ async function bootAndLoad(win) { return; } + clearTimeout(slowBootNote); const origin = serverManager.origin(); console.log( result.external diff --git a/desktop/main/server-manager.js b/desktop/main/server-manager.js index ab9f9e7..b85c5b3 100644 --- a/desktop/main/server-manager.js +++ b/desktop/main/server-manager.js @@ -109,16 +109,32 @@ class ServerManager { if (!wasStopping && this.onUnexpectedExit) this.onUnexpectedExit(code); }); - const ready = await waitForCampfire(this.port, 30000, () => this.child !== null); + // Generous deadline: on the very first launch Gatekeeper verifies the + // whole bundle (~400 MB) before and while the sidecar starts, which can + // take well over 30s on slower machines. + const ready = await waitForCampfire(this.port, 90000, () => this.child !== null); if (!ready) { this.stop(); throw new Error( - "The Campfire server did not start.\nSee ~/.campfire/logs/desktop-server.log for details.", + "The Campfire server did not start.\n\n" + + `Recent server log (~/.campfire/logs/desktop-server.log):\n${this.logTail()}`, ); } return { port: this.port, external: false }; } + /** Last few lines of the sidecar log, for error dialogs. */ + logTail(lines = 12) { + try { + const { readFileSync } = require("node:fs"); + const logPath = join(os.homedir(), ".campfire", "logs", "desktop-server.log"); + const content = readFileSync(logPath, "utf8"); + return content.split("\n").filter(Boolean).slice(-lines).join("\n"); + } catch { + return "(log unavailable)"; + } + } + /** Stop the sidecar (no-op when attached to an external server). */ stop() { if (!this.child) return; diff --git a/desktop/package.json b/desktop/package.json index c38995d..b8ea28a 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -1,6 +1,6 @@ { "name": "campfire-desktop", - "version": "0.4.0", + "version": "0.4.1", "private": true, "description": "Campfire desktop app — native macOS shell around the Campfire server", "license": "MIT", diff --git a/web/package.json b/web/package.json index 4c50c44..975929b 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "the-campfire", - "version": "0.4.0", + "version": "0.4.1", "type": "module", "description": "Campfire \u2014 collaborative web platform for AI coding agents. Run Claude Code, Codex, Goose, Aider, and more from one browser UI with real-time collaboration, permission voting, and automation.", "license": "MIT",