You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A codebase-memory-mcp stdio client process consumes 55-86% of one CPU core while completely idle, from the moment it starts, and never settles. Expected: ~0%, which is exactly what 0.9.0 does on the same machine.
This is not agent-specific and needs no MCP traffic to reproduce: spawning the binary from a plain shell and never sending a single JSON-RPC message already burns a core. The daemon itself is idle (1.2% of one core); the whole cost is in the client processes.
Because each editor/agent session spawns its own client, the cost multiplies. Snapshot from this machine with 5 client processes alive (one per open Claude Code session) plus the shared daemon:
PID % of one core RSS
4024 69.5 12.9 MB
37336 67.7 12.2 MB
24208 65.3 13.7 MB
21848 63.9 13.6 MB
10784 47.3 10.4 MB
20812 1.2 6.2 MB <- the daemon, idle as expected
sum: 3.15 cores = 26% of a 12-thread CPU, permanently, doing nothing
RAM is fine (~13 MB per client). This is purely CPU.
Reproduction
No agent, no editor, no handshake required.
On Windows, spawn the server with stdin held open as a pipe and write nothing to it:
// node spin.jsconst{ spawn, execSync }=require('child_process');constp=spawn("C:/path/to/codebase-memory-mcp.exe",[],{stdio: ['pipe','pipe','pipe']});p.stdout.on('data',()=>{});p.stderr.on('data',()=>{});constcpu=()=>parseFloat(execSync('powershell -NoProfile -Command "(Get-Process -Id '+p.pid+').TotalProcessorTime.TotalMilliseconds"').toString().trim());setTimeout(()=>{consta=cpu(),t=Date.now();setTimeout(()=>{constb=cpu();console.log(((b-a)/(Date.now()-t)*100).toFixed(1)+'% of one core');p.kill();},10000);},3000);
Run it against 0.9.0 and against 0.10.8.
Metric: delta of TotalProcessorTime over a 10 s wall-clock window, expressed as a percentage of a single core. Measurement starts 3 s after spawn so startup is excluded.
A/B, same machine, alternated runs
Real user profile, 9 indexed projects:
Version
run 1
run 2
run 3
0.9.0
0.0%
0.0%
0.1%
0.10.8
70.2%
76.4%
85.9%
Version bisect
Each version run with an isolated LOCALAPPDATA / APPDATA / USERPROFILE / HOME, so every binary got a private daemon endpoint and an empty state directory with zero indexed projects:
Version
% of one core
0.9.0
0.0 / 0.0 / 0.1
0.9.1-rc.1
27.0 / 29.5 / 39.1
0.10.0
26.8
0.10.1
17.3
0.10.2
20.0
0.10.4
34.6
0.10.6
30.4
0.10.8
22.7
The regression boundary is exactly v0.9.0 → v0.9.1-rc.1. Absolute numbers are lower in this table than in the A/B above because these runs had no indexed projects; the spin happens regardless.
Logs
Nothing. The loop is completely silent. Measured simultaneously over one 12 s window with CBM_LOG_LEVEL=trace set: 66.8% of one core, and 0 bytes on stderr (0 on stdout too).
A freshly spawned client shows the same shape: 6 threads, two of them at 50.3% and 54.1%.
Ruled out (each one measured, not assumed)
Number of concurrent sessions — a single client, alone, spawned from a plain shell, spins.
MCP traffic — spins before any initialize; 62.1% with no handshake at all vs 55.0% after one.
The daemon — daemon status reports all clients committed; the daemon uses 1.2% of a core.
auto_watch — no change with it set to false (47-64% either way). Note the watcher thread is created unconditionally in src/main.c (if (g_watcher)), so the config key does not gate it.
Indexed projects — the isolated-home runs above have zero projects and still spin.
ui_enabled — 64.4% with --ui=false.
--tool-profile=scout — 70.0%.
Warm-up — the thread breakdown above was taken on a client that had been alive for 24 minutes; it was still carrying 59.5% of a core across its two hot threads.
The parent-death watchdog — excluded on Windows (#ifndef _WIN32) and polls at 500 ms.
The watcher's own loop — POLL_BASE_MS 5000 chunked by SLEEP_CHUNK_MS 500 is 10 wakeups per cycle; far too cheap to account for this.
Where I would look (hypothesis, NOT verified)
I could not attribute the CPU to a specific line — the shipped binary is stripped and the loop logs nothing — so please treat this section as a starting point rather than a diagnosis.
The regression window v0.9.0..v0.9.1-rc.1 is 486 commits and contains 0e00ef57 ("feat: coordinate concurrent CBM sessions" — same title as the closed issue #1139, though the commit message itself carries no issue reference), which introduces all of src/daemon/, including src/daemon/frontend.c. That file is the client side: frontend.h describes it as "Stateless stdio bridge for a daemon-backed MCP session", and src/main.c:2946 calls cbm_daemon_frontend_mcp_run(..., stdin, stdout) in the client process. So in this window the client's stdio path went from having no daemon bridge at all to having one built on poll loops:
FRONTEND_WAIT_US = 1000 (1 ms) — 3 call sites, including the enqueue path and the join watchdog
FRONTEND_IDLE_WAIT_US = 10 * 1000 (10 ms) — the idle path of frontend_worker
FRONTEND_MAINTENANCE_POLL_MS = 10 — frontend_worker calls frontend_exit_for_maintenance()100 times per second, which calls cbm_version_cohort_maintenance_presence_terminal()
FRONTEND_IDLE_WAIT_US did not exist in 0e00ef57; it was added later, which may explain why the figure varies across 0.10.x instead of being flat.
so any argument below 1000 µs becomes Sleep(0), which does not sleep. All the named poll constants are >= 1000 µs, but version_cohort_retry_sleep() (src/daemon/version_cohort.c:93) passes VERSION_COHORT_RETRY_US / 2U + (mix % VERSION_COHORT_RETRY_US) = 500-1499 µs, so roughly half of its jitter range degrades to Sleep(0) on Windows. I do not think this is the driver here (a single isolated client has no lock contention and still spins), but it looked worth flagging.
Not a duplicate of
Very high CPU usage #45 "Very high CPU usage" — March 2026, macOS, four months before src/daemon/ existed.
CPU memory usage is too high #1084 "CPU memory usage is too high" — Windows, but triggered by turning auto-indexing on; here auto_index is false, and 0.9.0 measures 0.0% on the same configuration.
I also searched issues and PRs for frontend.c, FRONTEND_WAIT_US, FRONTEND_IDLE_WAIT_US, "stdio bridge", "idle cpu" and version_cohort and found nothing covering this. No commit on main after v0.10.8 touches frontend.c, version_cohort.c or main.c, and the three constants are unchanged on main, so this should still be live on tip.
The installed 0.10.8 binary is byte-identical to the codebase-memory-mcp-windows-amd64.zip release artifact — SHA256 b4b403b1d7c4def3785f148b93f345ce8427858f4f5489ce28580c4387a336a6, matching the build id the daemon reports. Every version in the bisect came from that same windows-amd64 release family, so no build-variant difference is involved. (Note that in 0.10.8 this "standard" artifact does serve the graph UI on 9749.)
The 0.9.0 binary came from the installer's own backup, %LOCALAPPDATA%\Programs\codebase-memory-mcp\codebase-memory-mcp.exe.old
Project scale (if relevant)
9 indexed projects on the affected machine. Also reproduces with 0 indexed projects (see bisect table), so scale is not a factor.
Confirmations
I searched existing issues and this is not a duplicate.
My reproduction uses shareable code (a dummy snippet or a public OSS repository), not proprietary code.
Version
codebase-memory-mcp 0.10.8
Platform
Windows (x64)
Install channel
GitHub release archive / install.sh / install.ps1
Binary variant
standard
What happened, and what did you expect?
A
codebase-memory-mcpstdio client process consumes 55-86% of one CPU core while completely idle, from the moment it starts, and never settles. Expected: ~0%, which is exactly what 0.9.0 does on the same machine.This is not agent-specific and needs no MCP traffic to reproduce: spawning the binary from a plain shell and never sending a single JSON-RPC message already burns a core. The daemon itself is idle (1.2% of one core); the whole cost is in the client processes.
Because each editor/agent session spawns its own client, the cost multiplies. Snapshot from this machine with 5 client processes alive (one per open Claude Code session) plus the shared daemon:
RAM is fine (~13 MB per client). This is purely CPU.
Reproduction
No agent, no editor, no handshake required.
Metric: delta of
TotalProcessorTimeover a 10 s wall-clock window, expressed as a percentage of a single core. Measurement starts 3 s after spawn so startup is excluded.A/B, same machine, alternated runs
Real user profile, 9 indexed projects:
Version bisect
Each version run with an isolated
LOCALAPPDATA/APPDATA/USERPROFILE/HOME, so every binary got a private daemon endpoint and an empty state directory with zero indexed projects:The regression boundary is exactly v0.9.0 → v0.9.1-rc.1. Absolute numbers are lower in this table than in the A/B above because these runs had no indexed projects; the spin happens regardless.
Logs
Nothing. The loop is completely silent. Measured simultaneously over one 12 s window with
CBM_LOG_LEVEL=traceset: 66.8% of one core, and 0 bytes on stderr (0 on stdout too).Diagnostics trajectory (memory / performance / leak issues)
Thread-level breakdown
One client, alive 24 minutes, still spinning. Two threads carry all of it:
A freshly spawned client shows the same shape: 6 threads, two of them at 50.3% and 54.1%.
Ruled out (each one measured, not assumed)
initialize; 62.1% with no handshake at all vs 55.0% after one.daemon statusreports all clients committed; the daemon uses 1.2% of a core.auto_watch— no change with it set tofalse(47-64% either way). Note the watcher thread is created unconditionally insrc/main.c(if (g_watcher)), so the config key does not gate it.auto_index—falsethroughout (this is what distinguishes it from CPU memory usage is too high #1084).ui_enabled— 64.4% with--ui=false.--tool-profile=scout— 70.0%.#ifndef _WIN32) and polls at 500 ms.POLL_BASE_MS 5000chunked bySLEEP_CHUNK_MS 500is 10 wakeups per cycle; far too cheap to account for this.Where I would look (hypothesis, NOT verified)
I could not attribute the CPU to a specific line — the shipped binary is stripped and the loop logs nothing — so please treat this section as a starting point rather than a diagnosis.
The regression window
v0.9.0..v0.9.1-rc.1is 486 commits and contains0e00ef57("feat: coordinate concurrent CBM sessions" — same title as the closed issue #1139, though the commit message itself carries no issue reference), which introduces all ofsrc/daemon/, includingsrc/daemon/frontend.c. That file is the client side:frontend.hdescribes it as "Stateless stdio bridge for a daemon-backed MCP session", andsrc/main.c:2946callscbm_daemon_frontend_mcp_run(..., stdin, stdout)in the client process. So in this window the client's stdio path went from having no daemon bridge at all to having one built on poll loops:FRONTEND_WAIT_US = 1000(1 ms) — 3 call sites, including the enqueue path and the join watchdogFRONTEND_IDLE_WAIT_US = 10 * 1000(10 ms) — the idle path offrontend_workerFRONTEND_MAINTENANCE_POLL_MS = 10—frontend_workercallsfrontend_exit_for_maintenance()100 times per second, which callscbm_version_cohort_maintenance_presence_terminal()FRONTEND_IDLE_WAIT_USdid not exist in0e00ef57; it was added later, which may explain why the figure varies across 0.10.x instead of being flat.Secondary observation, possibly unrelated
On Windows
cbm_usleepis integer division:so any argument below 1000 µs becomes
Sleep(0), which does not sleep. All the named poll constants are >= 1000 µs, butversion_cohort_retry_sleep()(src/daemon/version_cohort.c:93) passesVERSION_COHORT_RETRY_US / 2U + (mix % VERSION_COHORT_RETRY_US)= 500-1499 µs, so roughly half of its jitter range degrades toSleep(0)on Windows. I do not think this is the driver here (a single isolated client has no lock contention and still spins), but it looked worth flagging.Not a duplicate of
src/daemon/existed.auto_indexisfalse, and 0.9.0 measures 0.0% on the same configuration.cbm_nanosleepEINTR — POSIX only, and none of its four call sites isfrontend.c.I also searched issues and PRs for
frontend.c,FRONTEND_WAIT_US,FRONTEND_IDLE_WAIT_US, "stdio bridge", "idle cpu" andversion_cohortand found nothing covering this. No commit onmainafter v0.10.8 touchesfrontend.c,version_cohort.cormain.c, and the three constants are unchanged onmain, so this should still be live on tip.Environment
codebase-memory-mcp-windows-amd64.ziprelease artifact — SHA256b4b403b1d7c4def3785f148b93f345ce8427858f4f5489ce28580c4387a336a6, matching the build id the daemon reports. Every version in the bisect came from that samewindows-amd64release family, so no build-variant difference is involved. (Note that in 0.10.8 this "standard" artifact does serve the graph UI on 9749.)%LOCALAPPDATA%\Programs\codebase-memory-mcp\codebase-memory-mcp.exe.oldProject scale (if relevant)
9 indexed projects on the affected machine. Also reproduces with 0 indexed projects (see bisect table), so scale is not a factor.
Confirmations