Daemon leaks on Linux: `stat -f` is macOS-only + missing PID-file guard

Đang mở
#26 1 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

Đánh giá

Độ khó
3/5
Thời gian dự kiến
1-2 ngày
Mức phù hợp với người mới
55/100
Loại issue
Lỗi
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Ít trao đổi
Công nghệ
bash, bun, typescript
Lĩnh vực
cli, tooling

Hướng nghiên cứu

Bắt đầu với bin/macrodata-hook.sh, đặc biệt là store_lastmod, check_files_changed, is_daemon_running và start_daemon; so sánh hành vi của chúng với macrodata-daemon.ts trên Linux. Chạy hook trong môi trường Linux và xác minh rằng các kiểm tra mtime hoạt động, đồng thời các lần gọi hook lặp lại không tạo ra các tiến trình daemon trùng lặp, kể cả khi tệp PID không tồn tại hoặc đã cũ.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

Summary

On Linux (e.g. WSL2 / Ubuntu), bin/macrodata-hook.sh has two bugs that compound into a daemon leak: every session-start or prompt-submit hook call can spawn a fresh macrodata-daemon.ts that never exits. On a machine that was left running for ~26 days with scheduled dreamtime + morning-prep sessions firing nightly, I ended up with 9 concurrent daemons holding ~14% RSS each, OOM-thrashing and pinning CPU.

Bug 1 — stat -f %m is BSD/macOS syntax

bin/macrodata-hook.sh uses stat -f %m in store_lastmod and check_files_changed. On GNU coreutils (Linux) -f means display filesystem status, not format. The call errors out:

$ stat -f %m ~/some/file
stat: cannot read file system information for '%m': No such file or directory
  File: "/home/..."

Because the error is swallowed by 2>/dev/null, the command substitution returns the empty string. check_files_changed then compares "" against whatever is in .lastmod, always sees a mismatch, and reports "files changed" every prompt. This triggers signal_daemon_reload + re-injects static context on every hook fire.

GNU coreutils uses stat -c %Y. BSD/macOS coreutils uses stat -f %m. A portable helper handles both.

Bug 2 — No guard against duplicate daemon spawning when PID file is missing

is_daemon_running() only returns true when $PIDFILE exists and its PID is live. On my machine ~/.config/macrodata/.daemon.pid doesn't exist (possibly never written by the daemon, or removed, or the daemon is running with a different MACRODATA_ROOT). So is_daemon_running always returns false, and every start_daemon call spawns a new nohup bun run macrodata-daemon.ts & — which gets adopted by init when the parent shell dies and keeps running forever via the croner schedulers.

Over 26 days of scheduled sessions, this accumulated to 9 orphaned daemons:

PID       ETIME        %CPU %MEM CMD
317154    1-04:32:16   3.6  0.3  bun run .../macrodata-daemon.ts
317155    1-04:32:16   3.5  0.3  bun run .../macrodata-daemon.ts
317157    1-04:32:16   3.3  0.3  bun run .../macrodata-daemon.ts
373463    19:31:45     4.6  0.6  bun run .../macrodata-daemon.ts
435928    11:28:12     5.1  12.6 bun run .../macrodata-daemon.ts
453484    52:06        14.6 12.6 bun run .../macrodata-daemon.ts
453533    52:06        14.2 10.6 bun run .../macrodata-daemon.ts
462810    01:47        128  14.0 bun run .../macrodata-daemon.ts
463478    00:13        12.9 2.9  bun run .../macrodata-daemon.ts

Memory was 5.5/7.6 GiB used, 1.7/2 GiB swap used, 15-min load average 252.

Environment

  • Linux 6.6.87.2-microsoft-standard-WSL2 (Ubuntu on WSL2)
  • macrodata 0.2.1 (Claude Code plugin cache)
  • bash, GNU coreutils

Suggested fix

Patch applied locally to bin/macrodata-hook.sh:

  1. Portable mtime helper — try GNU stat -c %Y first, fall back to BSD stat -f %m:

    get_mtime() {
        stat -c %Y "$1" 2>/dev/null || stat -f %m "$1" 2>/dev/null || echo 0
    }
    

    Replace all stat -f %m "$X" 2>/dev/null || echo 0 call sites with get_mtime "$X".

  2. pgrep fallback in is_daemon_running — if PID file is missing/stale, check whether any live daemon exists for this user before spawning:

    is_daemon_running() {
        if [ -f "$PIDFILE" ]; then
            local pid=$(cat "$PIDFILE")
            if kill -0 "$pid" 2>/dev/null; then
                return 0
            fi
        fi
        if pgrep -u "$(id -u)" -f "macrodata-daemon\.ts" >/dev/null 2>&1; then
            return 0
        fi
        return 1
    }
    

Happy to open a PR if you'd like — wanted to file the report first in case you'd prefer a different shape (e.g. having the daemon write the PID file more defensively on startup, or adding flock around start_daemon).

Ngôn ngữ chính
TypeScript
Star
119
Fork
6
Merge trung bình
1 giờ 32 phút
Pull request đã merge (30 ngày)
2

Hướng dẫn đóng góp

Chưa lập chỉ mục được hướng dẫn đóng góp cho kho mã nguồn này

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của ascorbic/macrodata

Tất cả issue của ascorbic/macrodata

Issue tương tự

Thêm issue về TypeScript

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.