From 2141b647eaa7a5122d4d0eca80b53754bc178ad4 Mon Sep 17 00:00:00 2001 From: Ahmed <1140606+ahmedibrahim085@users.noreply.github.com> Date: Thu, 19 Feb 2026 22:08:02 +0000 Subject: [PATCH 1/3] refactor(daemon): use top-level os import instead of dynamic await import The `os` module was imported dynamically via `await import('os')` inside the async `canRunWorker()` method. Since `os` is a Node.js built-in that is always available and never changes at runtime, this dynamic import adds unnecessary overhead on every resource check call. Move to a standard top-level ESM import. This also enables synchronous access to `os` APIs (like `os.cpus()`) in non-async contexts such as the constructor, which the next commit requires for platform-aware threshold computation. --- v3/@claude-flow/cli/src/services/worker-daemon.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v3/@claude-flow/cli/src/services/worker-daemon.ts b/v3/@claude-flow/cli/src/services/worker-daemon.ts index f02c114f62..12a6f44014 100644 --- a/v3/@claude-flow/cli/src/services/worker-daemon.ts +++ b/v3/@claude-flow/cli/src/services/worker-daemon.ts @@ -13,6 +13,7 @@ import { EventEmitter } from 'events'; import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs'; import { join } from 'path'; +import os from 'os'; import { HeadlessWorkerExecutor, HEADLESS_WORKER_TYPES, @@ -233,7 +234,6 @@ export class WorkerDaemon extends EventEmitter { * Check if system resources allow worker execution */ private async canRunWorker(): Promise<{ allowed: boolean; reason?: string }> { - const os = await import('os'); const cpuLoad = os.loadavg()[0]; const totalMem = os.totalmem(); const freeMem = os.freemem(); From fb042025c09f83a62561caaa24f329f81f344ddf Mon Sep 17 00:00:00 2001 From: Ahmed <1140606+ahmedibrahim085@users.noreply.github.com> Date: Thu, 19 Feb 2026 22:08:31 +0000 Subject: [PATCH 2/3] fix(daemon): compute resource thresholds from system capabilities instead of hardcoded values MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Daemon workers (audit, optimize, testgaps) never execute on macOS because the hardcoded resource thresholds are incompatible with how macOS reports system metrics. CPU: os.loadavg() returns aggregate load across all cores. A 16-core Mac at modest utilization reports loadavg ~3-5, permanently exceeding the hardcoded maxCpuLoad of 2.0. Workers are blocked even when the system is effectively idle. Memory: macOS uses available RAM as filesystem cache. os.freemem() reports only truly unused memory (typically 5-8%), not reclaimable cache. The 20% minFreeMemoryPercent threshold is unreachable under normal macOS operation. Extract threshold computation into getDefaultResourceThresholds() that scales CPU threshold by core count (cores × 3) and uses a 1% free memory floor on Darwin. Linux gets a 5% floor since its memory reporting is more conservative. Both remain overridable via config parameter. Fixes #1077 --- .../cli/src/services/worker-daemon.ts | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/v3/@claude-flow/cli/src/services/worker-daemon.ts b/v3/@claude-flow/cli/src/services/worker-daemon.ts index 12a6f44014..1b45d13070 100644 --- a/v3/@claude-flow/cli/src/services/worker-daemon.ts +++ b/v3/@claude-flow/cli/src/services/worker-daemon.ts @@ -106,6 +106,34 @@ const DEFAULT_WORKERS: WorkerConfigInternal[] = [ // Worker timeout (5 minutes max per worker) const DEFAULT_WORKER_TIMEOUT_MS = 5 * 60 * 1000; +/** + * Compute platform-aware resource thresholds. + * + * The original hardcoded values (maxCpuLoad: 2.0, minFreeMemoryPercent: 20) + * permanently block all daemon workers on macOS because: + * + * - CPU: os.loadavg()[0] reports aggregate load across ALL cores. A 16-core + * Mac at 15% utilization per core reports loadavg ~2.4, exceeding the 2.0 + * threshold. Scaling by core count (cores × 3) allows workers to run under + * normal system load while still blocking under genuine resource pressure. + * + * - Memory: macOS aggressively caches files in RAM. os.freemem() reports only + * truly unused memory (typically 5-8%), not reclaimable cache. The 20% + * threshold is unreachable under normal macOS operation. A 1% threshold on + * Darwin reflects actual memory pressure, not cache utilization. + * + * See: https://github.com/ruvnet/claude-flow/issues/1077 + */ +function getDefaultResourceThresholds(): DaemonConfig['resourceThresholds'] { + const cpuCount = os.cpus().length; + const isDarwin = process.platform === 'darwin'; + + return { + maxCpuLoad: cpuCount * 3, + minFreeMemoryPercent: isDarwin ? 1 : 5, + }; +} + /** * Worker Daemon - Manages background workers with Node.js */ @@ -135,10 +163,7 @@ export class WorkerDaemon extends EventEmitter { stateFile: config?.stateFile ?? join(claudeFlowDir, 'daemon-state.json'), maxConcurrent: config?.maxConcurrent ?? 2, // P0 fix: Limit concurrent workers workerTimeoutMs: config?.workerTimeoutMs ?? DEFAULT_WORKER_TIMEOUT_MS, - resourceThresholds: config?.resourceThresholds ?? { - maxCpuLoad: 2.0, - minFreeMemoryPercent: 20, - }, + resourceThresholds: config?.resourceThresholds ?? getDefaultResourceThresholds(), workers: config?.workers ?? DEFAULT_WORKERS, }; From b64581952b0e9d710b8ab6a496c74cc8e26a1513 Mon Sep 17 00:00:00 2001 From: Ahmed <1140606+ahmedibrahim085@users.noreply.github.com> Date: Thu, 19 Feb 2026 22:08:55 +0000 Subject: [PATCH 3/3] feat(daemon): log computed resource thresholds at startup When debugging why workers are blocked, the first question is "what thresholds is the daemon using?" Currently this requires reading the daemon-state.json file after startup. Add a single log line at daemon start that shows the active maxCpuLoad, core count, minFreeMemoryPercent, and platform. This makes threshold misconfiguration immediately visible in daemon logs without additional tooling. --- v3/@claude-flow/cli/src/services/worker-daemon.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/v3/@claude-flow/cli/src/services/worker-daemon.ts b/v3/@claude-flow/cli/src/services/worker-daemon.ts index 1b45d13070..d95cd9d5d1 100644 --- a/v3/@claude-flow/cli/src/services/worker-daemon.ts +++ b/v3/@claude-flow/cli/src/services/worker-daemon.ts @@ -351,6 +351,9 @@ export class WorkerDaemon extends EventEmitter { this.startedAt = new Date(); this.emit('started', { pid: process.pid, startedAt: this.startedAt }); + const { maxCpuLoad, minFreeMemoryPercent } = this.config.resourceThresholds; + this.log('info', `Resource thresholds: maxCpuLoad=${maxCpuLoad} (${os.cpus().length} cores), minFreeMemoryPercent=${minFreeMemoryPercent}% (${process.platform})`); + // Schedule all enabled workers for (const workerConfig of this.config.workers) { if (workerConfig.enabled) {