fix(cli): report available memory instead of free memory in doctor#1204
Conversation
os.freemem() on macOS returns only truly free pages (~0.1 GB on a 24 GB machine), ignoring inactive/purgeable/speculative pages the kernel reclaims on demand. This caused a false "Low memory" warning on every macOS machine. Add getAvailableMemoryMb() that uses vm_stat on macOS and MemAvailable from /proc/meminfo on Linux, falling back to os.freemem() elsewhere. Also trim FFmpeg/FFprobe version strings to just "toolname X.Y.Z" instead of the full copyright line.
jrusso1020
left a comment
There was a problem hiding this comment.
LGTM. macOS available-memory formula is right (free + inactive + purgeable + speculative — matching what Activity Monitor reports under "Memory Available"; wired + active correctly excluded). Linux uses /proc/meminfo MemAvailable which is the canonical kernel-3.14+ field. Fallback to os.freemem() everywhere (Windows, vm_stat fail, /proc/meminfo missing, parse fail) is the right degradation — no crashes on weird environments.
What I checked
- macOS formula:
free + inactive + purgeable + speculative(pages) × pageSize. Wired and active are correctly excluded (kernel-locked / in-use). This matches the canonical Mach "available" definition. - Linux:
MemAvailableis the kernel-computed "what apps can actually claim" — superset of free that includes reclaimable cache. Correct field; correct kB → MB conversion (Math.trunc(.../1024)). - Fallback chain: try-block on both platforms catches
execSync/readFileSyncfailures and returnsbytesToMb(freemem()). The page-size parse miss (if (!pageSize) return fallback) and theMemAvailable:regex miss (return fallback) both degrade gracefully without crashing. parseToolVersionregex:/(ffmpeg|ffprobe)\s+version\s+([\d][\d.\-\w]*)/i— leading[\d]so anN-12345-abcdev-snapshot version falls through to the trimmed-input fallback (acceptable), and[\d.\-\w]*after that absorbs the gyan.dev7.1.1-essentials_build-www.gyan.devsuffix the test pins.- Tests:
vi.resetModules()+vi.doMock("node:os")per-test correctly isolates platform-mocked imports; the dynamicawait import("./system.js")after each mock setup picks up the fresh mocked deps. - Docker run-context (worth noting because hyperframes Docker workflows exist): in a Docker container running on Linux,
/proc/meminfois the host's by default but cgroup-aware tools (cgmemtop) report container limits instead. This change reports host MemAvailable, not cgroup-limited container memory — which is what users probably want for "does my dev box have enough RAM," but if a container has a 2GB limit on a 32GB host, the check would say "fine" while the container OOMs. Out-of-scope for this fix; flagging as future awareness, not a blocker.
Cross-platform
- macOS ✓ (vm_stat parse)
- Linux ✓ (MemAvailable)
- Windows: explicit fallback to
freemem(). On Windowsos.freemem()already wrapsGlobalMemoryStatusEx().ullAvailPhys, which IS "available physical" (free + standby) — so the value is correct even though the label still reads "available" (was "free" before). Net: Windows users get an accurate memory figure post-fix; the label change is the visible delta.
Nits — none
CI green across the board (CodeQL, CLI smoke, format/lint, preflight). Approving.
— Jerrai
vanceingalls
left a comment
There was a problem hiding this comment.
LGTM — clean fix for a real, user-facing papercut.
Design
- macOS formula
Pages free + inactive + purgeable + speculativematches Apple's "App Memory + Cached Files" mental model. Wired/active are correctly excluded. - Linux: parsing
MemAvailabledirectly is the kernel-authoritative answer — better than reconstructing fromMemFree + Buffers + Cached. - Threshold (
< 2048 MB) against available rather than free is more permissive, which is the whole point of the fix — matches what users would expect fromfree -h. - Windows path falls through to
os.freemem(), which on Win32 maps toGlobalMemoryStatusEx().ullAvailPhys— that's already "available physical memory" semantics, so labeling the fallback "GB available" is honest. Coverage is fine.
Correctness
?? "0"parse fallbacks degrade gracefully (undercount → safer false-positive on the low-memory warning, never a false-negative).Pages purgeableline presence varies by macOS version; this handles that correctly.parseToolVersionregex captures the Windows gyan.dev suffix, ffmpeg/ffprobe, and trims unrecognized input to a sensible fallback. Tests cover all four paths.- Tests use
vi.doMock+vi.resetModulesproperly to swapnode:os/node:child_process/node:fsper case. Coverage ongetAvailableMemoryMb()is thorough.
Follow-up (nit, not a blocker)
packages/cli/src/commands/render.ts:996still emitsmemoryFreeMb: bytesToMb(freemem())in render telemetry. Now thatdoctorreports available, the telemetry value diverges from what users see indoctor. The field name ismemoryFreeMbso keeping it asfreemem()is defensible, but worth a follow-up to either rename tomemoryAvailableMband switch togetAvailableMemoryMb(), or add a sibling field. Whichever — just calling it out so the divergence is intentional.
Minor
parseToolVersionregex[\d][\d.\-\w]*— the leading[\d]character class is redundant with a bare\d. Style nit.
CI is green on all required checks. Ship it.
— Review by Vai
Summary
hyperframes doctormemory check reporting ~0.1 GB free on macOS machines with 24 GB RAM.os.freemem()only counts truly free pages, ignoring inactive/purgeable/speculative pages the kernel reclaims on demand. AddedgetAvailableMemoryMb()that usesvm_staton macOS andMemAvailablefrom/proc/meminfoon Linux, falling back toos.freemem()elsewhere.Before / After
Before:
After:
The memory check was the critical fix —
os.freemem()on macOS only counts truly free pages, ignoring ~4.9 GB of inactive/purgeable/speculative pages that the kernel reclaims instantly on demand. Every macOS user was getting a false "Low memory" warning. The new code parsesvm_staton macOS and/proc/meminfoon Linux to get the actual available memory.Test plan
getAvailableMemoryMb()covering macOS vm_stat parsing, Linux /proc/meminfo parsing, fallback behavior on parse errors, and unsupported platformsparseToolVersion()covering ffmpeg/ffprobe extraction, Windows gyan.dev builds, and unrecognized input fallbacknpx hyperframes doctorandnpx hyperframes doctor --jsonshow correct values