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.js → handleNewTopic(), 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
- Set
workingDir: "~" in ~/.botmux/bots.json
- Restart daemon:
botmux restart
- Send any message (e.g. "hi") to the bot in a Feishu thread
- Observe: daemon log stops after
Created session <uuid>, no Worker forked line ever appears
botmux send --session-id <sid> still works (IPC is on a separate thread), but the bot never responds
- 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:
- Skip scan when workingDir is home directory — treat
~ / $HOME as "no repo scan, fork directly" (like the no-projects path)
- Add a timeout to
scanMultipleProjects — abort after N seconds and proceed with empty project list
- Move scan to a worker thread — don't block the main event loop
- 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)
Summary
When
workingDiris set to~(home directory) inbots.json, every new topic triggersscanMultipleProjectswhich 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.js→handleNewTopic(), whenpinnedWorkingDirisundefined(no oncall binding, no bot default dir), the code falls through to the repo selection branch:With
workingDir: "~",getProjectScanDirs()returns["/Users/<user>"].scanMultipleProjectsis a synchronous function that recursively scans the entire home directory to depth 3, looking for.gitdirectories. On a typical macOS home directory (containingnode_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:
psshows it running, 0% CPU during I/O wait)online, 0 restartsbotmux send,botmux delete) still worksNew session:/Created sessionThis is NOT a deadlock — it's synchronous CPU/disk I/O blocking. It mimics a hung process perfectly.
Reproduction
workingDir: "~"in~/.botmux/bots.jsonbotmux restartCreated session <uuid>, noWorker forkedline ever appearsbotmux send --session-id <sid>still works (IPC is on a separate thread), but the bot never respondsDiagnosis Trace
Added
logger.infocheckpoints around everyawaitinhandleNewTopic:Fix Applied (Workaround)
Changed
workingDirfrom~to/tmpinbots.json. With/tmp(no git repos),scanMultipleProjectsreturns empty instantly → falls to theelse(no projects) path →forkWorkerfires immediately → bot responds normally.Verification after fix:
Suggested Fixes
Any of these would prevent the issue:
~/$HOMEas "no repo scan, fork directly" (like the no-projects path)scanMultipleProjects— abort after N seconds and proceed with empty project listworkingDir: "~"at setup time — the home directory is almost never a useful repo scan rootEnvironment
~(home directory with ~50k+ files)