fix(desktop): fix Claude agent spawn from managed runtime and rotate logs on restart

- Always override spawnClaudeCodeProcess to use process.execPath instead of
  SDK's PATH-based "node" lookup, which fails in the managed runtime bundle
- Fix append-mode arg ordering in resolveClaudeSpawnCommand so extra CLI args
  (e.g. --chrome) go after cli.js, not before it (Node exit code 9)
- Rotate daemon.log on every daemon restart for clean startup logs
- Remove dead dev_resource_root fallback from runtime_manager.rs
- Canonicalize current_exe path in CLI runner
This commit is contained in:
Mohamed Boudra
2026-03-12 13:16:08 +07:00
parent 9e6b45e2f0
commit 213f155c9c
3 changed files with 44 additions and 27 deletions
@@ -376,7 +376,7 @@ function resolveClaudeSpawnCommand(
if (commandConfig.mode === "append") {
return {
command: spawnOptions.command,
args: [...(commandConfig.args ?? []), ...spawnOptions.args],
args: [...spawnOptions.args, ...(commandConfig.args ?? [])],
};
}
@@ -390,15 +390,6 @@ function applyRuntimeSettingsToClaudeOptions(
options: ClaudeOptions,
runtimeSettings?: ProviderRuntimeSettings
): ClaudeOptions {
const hasEnvOverrides = Object.keys(runtimeSettings?.env ?? {}).length > 0;
const commandMode = runtimeSettings?.command?.mode;
const needsCustomSpawn =
hasEnvOverrides || commandMode === "append" || commandMode === "replace";
if (!needsCustomSpawn) {
return options;
}
return {
...options,
spawnClaudeCodeProcess: (spawnOptions) => {
@@ -406,7 +397,13 @@ function applyRuntimeSettingsToClaudeOptions(
spawnOptions,
runtimeSettings
);
return spawn(resolved.command, resolved.args, {
// The SDK defaults to spawning "node" via PATH lookup, which fails when
// running from the managed runtime bundle where node isn't in PATH.
// Always use process.execPath — the actual node binary running the daemon.
const command = resolved.command === spawnOptions.command
? process.execPath
: resolved.command;
return spawn(command, resolved.args, {
cwd: spawnOptions.cwd,
env: applyProviderEnv(spawnOptions.env, runtimeSettings),
signal: spawnOptions.signal,
+34 -2
View File
@@ -1,4 +1,4 @@
import { mkdirSync } from "node:fs";
import { existsSync, mkdirSync, readdirSync, renameSync, unlinkSync } from "node:fs";
import path from "node:path";
import pino from "pino";
import pretty from "pino-pretty";
@@ -138,6 +138,37 @@ function normalizeLoggerConfigInput(config: LoggerConfigInput): PersistedConfig
return config as PersistedConfig;
}
function rotateOnRestart(filePath: string, maxFiles: number): void {
if (!existsSync(filePath)) return;
const dir = path.dirname(filePath);
const base = path.basename(filePath);
const now = new Date();
const pad = (n: number) => String(n).padStart(2, "0");
const ts = `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}`;
try {
renameSync(filePath, path.join(dir, `${ts}-00-${base}`));
} catch {
return;
}
// Clean up old rotated logs beyond maxFiles.
// Both our restart-rotated files (YYYYMMDD-HHMM-00-daemon.log) and
// rotating-file-stream's size-rotated files (YYYYMMDD-HHMM-NN-daemon.log)
// end with -${base} and sort chronologically by name.
const rotatedFiles = readdirSync(dir)
.filter((f) => f.endsWith(`-${base}`) && f !== base)
.sort()
.reverse();
for (const file of rotatedFiles.slice(maxFiles)) {
try {
unlinkSync(path.join(dir, file));
} catch {}
}
}
function toRotatingFileStreamSize(size: string): string {
const trimmed = size.trim();
const match = trimmed.match(/^(\d+)\s*([bBkKmMgG])?$/);
@@ -230,11 +261,12 @@ export function createRootLogger(
})
: pino.destination({ dest: 1, sync: false });
rotateOnRestart(config.file.path, config.file.rotate.maxFiles);
const fileStream = createRotatingFileStream(path.basename(config.file.path), {
path: path.dirname(config.file.path),
size: toRotatingFileStreamSize(config.file.rotate.maxSize),
maxFiles: config.file.rotate.maxFiles,
initialRotation: true,
});
return pino(