From 4eea0e244dbccf5af37e9af263a35b2e8c6e4c5d Mon Sep 17 00:00:00 2001 From: clagentic <10177887+akuehner@users.noreply.github.com> Date: Tue, 9 Jun 2026 17:51:45 -0400 Subject: [PATCH] =?UTF-8?q?fix(ipc):=20ensure=20socket=20parent=20dir=20ex?= =?UTF-8?q?ists=20before=20bind=20=E2=80=94=20daemon=20crash-loop=20on=20f?= =?UTF-8?q?resh=20install=20(lr-88fe)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The daemon binds ~/.clagentic/console/daemon.sock but nothing in its own startup path guaranteed console/ existed. On fresh installs or systemd-driven starts (no prior CLI run), the bind fails with ENOENT, the IPC error handler hard-exits, and the supervisor crash-loops until it gives up. Three-layer fix: - ipc.js createIPCServer(): mkdirSync(dirname(sockPath)) before unlink/listen - ipc.js error handler: retry once on ENOENT (create dir + relisten) alongside the existing EADDRINUSE retry - daemon.js: call ensureConfigDir() before the IPC server bind so the daemon is self-sufficient regardless of which path forked it Co-Authored-By: Claude Sonnet 4.6 --- lib/daemon.js | 5 ++++- lib/ipc.js | 12 ++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/daemon.js b/lib/daemon.js index 1972368b..817190aa 100644 --- a/lib/daemon.js +++ b/lib/daemon.js @@ -23,7 +23,7 @@ delete process.env.CLAUDECODE; var fs = require("fs"); var path = require("path"); -var { loadConfig, saveConfig, socketPath, generateSlug, syncClayrc, removeFromClayrc, writeCrashInfo, readCrashInfo, clearCrashInfo, isPidAlive, clearStaleConfig, REAL_HOME } = require("./config"); +var { loadConfig, saveConfig, socketPath, ensureConfigDir, generateSlug, syncClayrc, removeFromClayrc, writeCrashInfo, readCrashInfo, clearCrashInfo, isPidAlive, clearStaleConfig, REAL_HOME } = require("./config"); var { createIPCServer } = require("./ipc"); var { createServer, generateAuthToken } = require("./server"); var { checkAclSupport, grantProjectAccess, revokeProjectAccess, provisionAllUsers, provisionLinuxUser, grantAllUsersAccess, deactivateLinuxUser, ensureProjectsDir } = require("./os-users"); @@ -1150,6 +1150,9 @@ if (existingConfig && existingConfig.pid && existingConfig.pid !== process.pid) clearStaleConfig(); } } +// Ensure config dirs exist before binding the socket. The daemon may start +// without a prior CLI invocation (systemd, supervisor), so it must be self-sufficient. +ensureConfigDir(); var ipc = createIPCServer(socketPath(), function (msg) { console.log("[daemon] IPC:", msg.cmd); switch (msg.cmd) { diff --git a/lib/ipc.js b/lib/ipc.js index 301f7c2f..87964029 100644 --- a/lib/ipc.js +++ b/lib/ipc.js @@ -6,6 +6,12 @@ var fs = require("fs"); * handler(msg) should return a response object (or a Promise of one). */ function createIPCServer(sockPath, handler) { + // Ensure the socket's parent directory exists (e.g. ~/.clagentic/console/). + // The daemon may start without a prior CLI invocation, so it must be self-sufficient. + if (process.platform !== "win32") { + try { fs.mkdirSync(require("path").dirname(sockPath), { recursive: true }); } catch (e) {} + } + // Remove stale socket file (not needed for Windows named pipes) if (process.platform !== "win32") { try { fs.unlinkSync(sockPath); } catch (e) {} @@ -53,6 +59,12 @@ function createIPCServer(sockPath, handler) { console.log("[ipc] Socket in use, removing stale socket and retrying..."); try { fs.unlinkSync(sockPath); } catch (e) {} server.listen(sockPath); + } else if (err.code === "ENOENT" && !retried) { + // Parent directory missing — create it and retry once. + retried = true; + console.log("[ipc] Socket directory missing, creating and retrying..."); + try { fs.mkdirSync(require("path").dirname(sockPath), { recursive: true }); } catch (e) {} + server.listen(sockPath); } else { console.error("[ipc] Failed to bind socket:", err.message); process.exit(1);