Skip to content

scanMultipleProjects on workingDir="~" blocks event loop, causing daemon to appear hung #363

Description

@lcy0108

Summary

When workingDir is set to ~ (home directory) in bots.json, every new topic triggers scanMultipleProjects which synchronously scans the entire home directory tree (depth=3) for git repositories. This blocks the Node event loop for minutes, causing the daemon to appear hung — the process is alive, IPC is responsive, but no Lark WebSocket events are processed and no log output is written.

Root Cause

In daemon.jshandleNewTopic(), when pinnedWorkingDir is undefined (no oncall binding, no bot default dir), the code falls through to the repo selection branch:

const scanDirs = getProjectScanDirs(ds).filter(d => existsSync(d));
let projects = [];
if (scanDirs.length > 0) {
    projects = scanMultipleProjects(scanDirs, 3, repoPickerScanOptions());
}

With workingDir: "~", getProjectScanDirs() returns ["/Users/<user>"]. scanMultipleProjects is a synchronous function that recursively scans the entire home directory to depth 3, looking for .git directories. On a typical macOS home directory (containing node_modules, .cache, Library/, Chrome profiles, etc.), this scan processes tens of thousands of directory entries and blocks the event loop for several minutes.

During this blocking period:

  • The daemon process is alive (ps shows it running, 0% CPU during I/O wait)
  • PM2 reports online, 0 restarts
  • IPC server (botmux send, botmux delete) still works
  • But no Lark WS events are dispatched — the event loop is blocked
  • No log lines are written after New session: / Created session
  • The user's messages are never processed → bot doesn't reply

This is NOT a deadlock — it's synchronous CPU/disk I/O blocking. It mimics a hung process perfectly.

Reproduction

  1. Set workingDir: "~" in ~/.botmux/bots.json
  2. Restart daemon: botmux restart
  3. Send any message (e.g. "hi") to the bot in a Feishu thread
  4. Observe: daemon log stops after Created session <uuid>, no Worker forked line ever appears
  5. botmux send --session-id <sid> still works (IPC is on a separate thread), but the bot never responds
  6. Wait 5-10+ minutes — eventually the scan completes and the worker forks, but by then the user has given up

Diagnosis Trace

Added logger.info checkpoints around every await in handleNewTopic:

[handleNewTopic] pre-quota-check          ✓ (instant)
[handleNewTopic] pre-download             ✓ (instant)
[handleNewTopic] pre-resolveSender        ✓ (instant, 1ms)
[handleNewTopic] post-resolveSender       ✓ (instant, cached)
[handleNewTopic] pre-resolvePinnedWorkingDir ✓ (instant)
[handleNewTopic] post-resolvePinnedWorkingDir pinned=none  ← falls to else branch
[handleNewTopic] else-branch: pre-replyInvalidWorkingDirs  ✓ (instant)
[handleNewTopic] else-branch: pre-scanMultipleProjects scanDirs=/Users/daoke
  ← LOG STOPS HERE FOR MINUTES ←

Fix Applied (Workaround)

Changed workingDir from ~ to /tmp in bots.json. With /tmp (no git repos), scanMultipleProjects returns empty instantly → falls to the else (no projects) path → forkWorker fires immediately → bot responds normally.

- "workingDir": "~"
+ "workingDir": "/tmp"

Verification after fix:

[f031f6fe] Worker forked (pid: 42770, active: 1)              ✓
Session f031f6fe ready (no projects to select), total active: 1 ✓
[f031f6fe:out] Worker started, waiting for init...             ✓

Suggested Fixes

Any of these would prevent the issue:

  1. Skip scan when workingDir is home directory — treat ~ / $HOME as "no repo scan, fork directly" (like the no-projects path)
  2. Add a timeout to scanMultipleProjects — abort after N seconds and proceed with empty project list
  3. Move scan to a worker thread — don't block the main event loop
  4. Warn on workingDir: "~" at setup time — the home directory is almost never a useful repo scan root

Environment

  • botmux v2.101.1
  • macOS 14.3.0 (Darwin arm64)
  • Node 22
  • workingDir: ~ (home directory with ~50k+ files)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions