Skip to content

Daily OOM crash-loop: worker RSS far exceeds its own memory budget, then full reindex retriggers on every crash #1864

Description

@jbmx77

Summary

The daemon repeatedly gets OOM-killed while indexing a mid-size repo (~9k files), and after each kill the next scheduled reindex treats the on-disk DB as invalid and reruns a full reindex instead of resuming/incremental, causing a daily OOM crash loop that has persisted for weeks.

Environment

  • codebase-memory-mcp v0.10.8
  • Linux x86_64, 15GB total RAM (total_ram_mb=15613)
  • Repo being indexed: ~9096 files (pipeline.discover files=9096)

What happened

journalctl -k shows the kernel OOM-killer terminating the codebase-memory process (not --cbm-daemon-internal wrapper, the index worker itself) on 10+ separate days between 2026-07-15 and 2026-08-27, always around the same daily trigger time, with RSS between 5.6GB and 7.1GB:

Aug 27 07:23:31 kernel: Out of memory: Killed process 534979 (codebase-memory) total-vm:15045260kB, anon-rss:7082876kB ...
Aug 26 07:22:37 kernel: Out of memory: Killed process 2007241 (codebase-memory) total-vm:12947920kB, anon-rss:6201692kB ...
Aug 25 07:27:59 kernel: Out of memory: Killed process 3482383 (codebase-memory) total-vm:10850636kB, anon-rss:5667976kB ...
Aug 24 07:25:04 kernel: Out of memory: Killed process 765588 (codebase-memory) total-vm:14661524kB, anon-rss:6118132kB ...
Aug 22 07:21:04 kernel: Out of memory: Killed process 3712834 (codebase-memory) total-vm:13960104kB, anon-rss:5824416kB ...
Aug 21 07:23:59 kernel: Out of memory: Killed process 980081 (codebase-memory) total-vm:15476024kB, anon-rss:6758460kB ...
Aug 20 07:20:57 kernel: Out of memory: Killed process 2410488 (codebase-memory) total-vm:14279952kB, anon-rss:6112296kB ...

Notably the worker's own log reports a much smaller intended budget:

level=info msg=mem.init budget_mb=975 total_ram_mb=15613 source=daemon_worker_cap
level=info msg=parallel.mem.budget total_mb=975 per_worker_mb=121

and the daemon itself:

level=info msg=daemon.start version=0.10.8 memory_budget_bytes=4093071360 physical_job_limit=4 worker_memory_budget_bytes=1023267840
level=info msg=mem.init budget_mb=3903 total_ram_mb=15613 source=ram_fraction

So the process's own accounting caps it at ~975MB/worker (~3.9GB budget total), but actual RSS reaches 5.6–7.1GB — ~1.5-7x over its own stated budget. The budget appears to be advisory/accounting only and isn't enforced (no rlimit/cgroup self-imposed by the process), so it doesn't actually prevent the OOM.

The crash-loop mechanism

After each OOM kill, the in-progress DB write is left as a .stage.<random> file of 0 bytes (confirmed 3 consecutive days: home-ubuntu-hermes-agent.db.stage.2TZ0Qq, ...5QSpI1, ...PIBqTb, one per day, all 0 bytes). The next run then logs:

level=warn msg=pipeline.route path=full reason=invalid_existing_db
level=info msg=pipeline.route path=full

i.e. it treats the corrupted/incomplete DB as unusable and reruns a full reindex of all 9096 files from scratch — reproducing the same memory spike and getting OOM-killed again. This has repeated daily for at least 6 weeks with no self-recovery, because there's no partial/incremental-index fallback and no automatic re-attempt with reduced parallelism/budget after a detected crash.

Suggested fixes

  1. Enforce the computed memory budget (e.g. via setrlimit/cgroup self-limit, or backpressure that pauses extraction workers) instead of only accounting it, so the process degrades gracefully instead of triggering the kernel OOM killer.
  2. On invalid_existing_db, don't unconditionally do a full reindex — if the failure was itself caused by OOM/crash, retry with a smaller worker count / lower per-file retention before falling back to full.
  3. Write the DB more atomically / checkpoint incrementally so a crash mid-index doesn't invalidate everything already indexed.
  4. Expose the memory budget/parallelism as user-configurable via codebase-memory-mcp config set (currently only auto_index, auto_watch, ui_* are configurable) so operators on memory-constrained hosts can cap it below the automatic ram_fraction default.

Workaround applied

Deleted the stale .stage files to break the loop, and added MemoryHigh/MemoryMax cgroup limits to the parent systemd units that spawn the MCP daemon, so a future runaway is contained to that unit's cgroup instead of triggering the global kernel OOM killer (which was also killing unrelated sibling services on the same host).

Metadata

Metadata

Assignees

No one assigned

    Labels

    parsing/qualityGraph extraction bugs, false positives, missing edgesstability/performanceServer crashes, OOM, hangs, high CPU/memory

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions