From a9a5eed0d5e1a7beaa784ae9ce7d74aa23c7293d Mon Sep 17 00:00:00 2001 From: clagentic <10177887+akuehner@users.noreply.github.com> Date: Thu, 11 Jun 2026 18:00:06 -0400 Subject: [PATCH] fix(config): move daemon.json to console/ subdir, decouple CLAGENTIC_HOME from CLAGENTIC_CONFIG (lr-eb5a) Completes the ~/.clagentic// subdir convention from lr-88fe. daemon.json was blocked from moving because CLAGENTIC_HOME was derived from dirname(CLAGENTIC_CONFIG), so moving the file would have pointed CLAGENTIC_HOME at ~/.clagentic/console/ and broken all other data paths. Fix: CLI now always passes CLAGENTIC_HOME explicitly when spawning the daemon. CLAGENTIC_CONFIG becomes just a file path with no home-derivation side effect. configPath() now returns ~/.clagentic/console/daemon[-dev].json. One-time copy-not-rename migration shim in ensureConfigDir() copies the old daemon.json to the new location on first run. Removes the lr-dec3 guard (process.exit(78) on console/-subdir detection) -- that guard was built to catch the broken intermediate state this PR intentionally resolves. With CLAGENTIC_HOME explicit, the false-positive condition can never occur. --- bin/cli.js | 9 ++++++++- lib/config.js | 18 ++++++++++++++++++ lib/daemon.js | 22 ++-------------------- test/config.test.js | 8 ++++++++ 4 files changed, 36 insertions(+), 21 deletions(-) diff --git a/bin/cli.js b/bin/cli.js index 3feba5e..f6eefb8 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -31,7 +31,7 @@ if (_isDev || process.argv.includes("--debug")) { } var crypto = require("crypto"); -var { loadConfig, saveConfig, configPath, socketPath, oldSocketPath, logPath, ensureConfigDir, isDaemonAlive, isDaemonAliveAsync, checkOldDaemon, generateSlug, clearStaleConfig, loadClayrc, saveClayrc, readCrashInfo, REAL_HOME } = require("../lib/config"); +var { loadConfig, saveConfig, configPath, socketPath, oldSocketPath, logPath, ensureConfigDir, isDaemonAlive, isDaemonAliveAsync, checkOldDaemon, generateSlug, clearStaleConfig, loadClayrc, saveClayrc, readCrashInfo, REAL_HOME, CONFIG_DIR } = require("../lib/config"); var { sendIPCCommand } = require("../lib/ipc"); var { generateAuthToken } = require("../lib/server"); var { enableMultiUser, disableMultiUser, hasAdmin, isMultiUser, getSetupCode } = require("../lib/users"); @@ -523,6 +523,7 @@ async function restartDaemonFromConfig() { // Debug mode: run in foreground with logs to stdout if (debugMode) { process.env.CLAGENTIC_CONFIG = configPath(); + process.env.CLAGENTIC_HOME = CONFIG_DIR; newConfig.pid = process.pid; saveConfig(newConfig); require(daemonScript); @@ -538,6 +539,7 @@ async function restartDaemonFromConfig() { stdio: ["ignore", logFd, logFd], env: Object.assign({}, process.env, { CLAGENTIC_CONFIG: configPath(), + CLAGENTIC_HOME: CONFIG_DIR, }), }); child.unref(); @@ -1624,6 +1626,7 @@ async function forkDaemon(mode, keepAwake, extraProjects, addCwd, wantOsUsers) { // Debug mode: run in foreground with logs to stdout if (debugMode) { process.env.CLAGENTIC_CONFIG = configPath(); + process.env.CLAGENTIC_HOME = CONFIG_DIR; config.pid = process.pid; saveConfig(config); require(daemonScript); @@ -1639,6 +1642,7 @@ async function forkDaemon(mode, keepAwake, extraProjects, addCwd, wantOsUsers) { stdio: ["ignore", logFd, logFd], env: Object.assign({}, process.env, { CLAGENTIC_CONFIG: configPath(), + CLAGENTIC_HOME: CONFIG_DIR, }), }); child.unref(); @@ -1827,6 +1831,7 @@ async function devMode(mode, keepAwake, existingPinHash, wantOsUsers) { stdio: ["ignore", "inherit", "inherit"], env: Object.assign({}, process.env, { CLAGENTIC_CONFIG: configPath(), + CLAGENTIC_HOME: CONFIG_DIR, }), }); @@ -1988,6 +1993,7 @@ async function restartDaemonWithTLS(config, callback) { stdio: ["ignore", logFd, logFd], env: Object.assign({}, process.env, { CLAGENTIC_CONFIG: configPath(), + CLAGENTIC_HOME: CONFIG_DIR, }), }); child.unref(); @@ -2014,6 +2020,7 @@ async function restartDaemonWithTLS(config, callback) { stdio: ["ignore", logFd2, logFd2], env: Object.assign({}, process.env, { CLAGENTIC_CONFIG: configPath(), + CLAGENTIC_HOME: CONFIG_DIR, }), }); child2.unref(); diff --git a/lib/config.js b/lib/config.js index 2dabfcc..b6fec13 100644 --- a/lib/config.js +++ b/lib/config.js @@ -67,6 +67,11 @@ var OLD_CRASH_INFO_PATH = path.join(CONFIG_DIR, "crash.json"); var _devMode = !!(process.env.CLAGENTIC_DEV || process.env.CLAY_DEV); function configPath() { + return path.join(CONFIG_DIR, "console", _devMode ? "daemon-dev.json" : "daemon.json"); +} + +// Path used before lr-eb5a moved daemon.json into console/. +function oldConfigPath() { return path.join(CONFIG_DIR, _devMode ? "daemon-dev.json" : "daemon.json"); } @@ -138,6 +143,18 @@ function ensureConfigDir() { console.error("[config] Migration of external-triggers/ failed (non-fatal):", e.message); } } + // daemon.json / daemon-dev.json (lr-eb5a: moved into console/) + var oldCfg = oldConfigPath(); + var newCfg = configPath(); + if (fs.existsSync(oldCfg) && !fs.existsSync(newCfg)) { + try { + fs.copyFileSync(oldCfg, newCfg); + try { fs.chmodSync(newCfg, 0o600); } catch (_) {} + console.warn("[config] Migrated " + oldCfg + " → " + newCfg); + } catch (e) { + console.error("[config] Migration of daemon.json failed (non-fatal):", e.message); + } + } } // Guard stale old socket cleanup: only unlink when the old-path daemon is @@ -422,6 +439,7 @@ module.exports = { CONFIG_DIR: CONFIG_DIR, EXTERNAL_TRIGGERS_DIR: EXTERNAL_TRIGGERS_DIR, configPath: configPath, + oldConfigPath: oldConfigPath, socketPath: socketPath, oldSocketPath: oldSocketPath, logPath: logPath, diff --git a/lib/daemon.js b/lib/daemon.js index 2fc2a71..f42ffd8 100644 --- a/lib/daemon.js +++ b/lib/daemon.js @@ -47,25 +47,6 @@ try { config = JSON.parse(fs.readFileSync(configFile, "utf8")); } catch (e) { if (e.code === "ENOENT") { - // Guard: detect CLAGENTIC_HOME mis-pointed at the socket subdirectory. - // - // Symptom: CLAGENTIC_HOME=~/.clagentic/console → CONFIG_DIR ends in - // ".clagentic/console" and configFile = CONFIG_DIR/daemon.json (absent), - // but the real config lives one level up at ../daemon.json. If we - // bootstrap here we silently write projects:[] and the user loses all - // their projects with no obvious error. Refuse loudly instead. (lr-dec3) - var _configBasename = path.basename(path.dirname(configFile)); - var _configGrandparent = path.basename(path.dirname(path.dirname(configFile))); - var _siblingConfig = path.join(path.dirname(path.dirname(configFile)), path.basename(configFile)); - if (_configBasename === "console" && _configGrandparent === ".clagentic" && fs.existsSync(_siblingConfig)) { - console.error("[daemon] ERROR: config file not found at " + configFile + " but a config exists at " + _siblingConfig + "."); - console.error("[daemon] CLAGENTIC_HOME appears to be set to the socket subdirectory (~/.clagentic/console)."); - console.error("[daemon] Do NOT set CLAGENTIC_HOME to ~/.clagentic/console — that directory holds the"); - console.error("[daemon] Unix socket, not the config. Relocating CLAGENTIC_HOME there moves daemon.json"); - console.error("[daemon] out of reach and causes your project list to be wiped on next bootstrap."); - console.error("[daemon] Fix: unset CLAGENTIC_HOME (or set it back to ~/.clagentic) and restart."); - process.exit(78); // EX_CONFIG — fatal config error, don't auto-restart - } // No config file yet (fresh install, systemd-only start). Bootstrap minimal defaults. console.warn("[daemon] No config file found at " + configFile + " — bootstrapping defaults. Run clagentic-console to configure projects."); var _isDev = !!(process.env.CLAGENTIC_DEV || process.env.CLAY_DEV); @@ -1630,7 +1611,7 @@ function spawnAndRestart() { relay.server.close(function () { try { var { spawn: spawnRestart } = require("child_process"); - var { logPath: restartLogPath, configPath: restartConfigPath } = require("./config"); + var { logPath: restartLogPath, configPath: restartConfigPath, CONFIG_DIR: restartConfigDir } = require("./config"); var daemonScript = path.join(__dirname, "daemon.js"); var logFd = fs.openSync(restartLogPath(), "a"); var child = spawnRestart(process.execPath, [daemonScript], { @@ -1639,6 +1620,7 @@ function spawnAndRestart() { stdio: ["ignore", logFd, logFd], env: Object.assign({}, process.env, { CLAGENTIC_CONFIG: restartConfigPath(), + CLAGENTIC_HOME: restartConfigDir, }), }); child.unref(); diff --git a/test/config.test.js b/test/config.test.js index e77492b..883ecbb 100644 --- a/test/config.test.js +++ b/test/config.test.js @@ -52,3 +52,11 @@ test("EXTERNAL_TRIGGERS_DIR is inside ~/.clagentic/console/", function () { "expected EXTERNAL_TRIGGERS_DIR to contain console/external-triggers, got: " + config.EXTERNAL_TRIGGERS_DIR ); }); + +test("configPath returns path inside ~/.clagentic/console/", function () { + var cp = config.configPath(); + assert.ok( + cp.includes(path.join("console", "daemon.json")), + "expected configPath() to contain console/daemon.json, got: " + cp + ); +});