You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
🤖 Suspend tool calls until workspace init completes (#455)
## Problem
SSH workspaces require asynchronous initialization (rsync, checkout,
init hook) before tools can execute. Without proper synchronization,
tools may race with initialization and fail with confusing errors.
## Solution
**Promise-based init waiting**: Tools wait for workspace initialization
to complete before executing. If init fails or times out (5 minutes),
tools proceed anyway and fail naturally with their own errors (better UX
than blocking).
### Architecture
**`InitStateManager`** - Tracks init state with promise-based waiting:
- `startInit(workspaceId, hookPath)` - Creates completion promise
- `endInit(workspaceId, exitCode)` - Resolves promise (success or
failure)
- `waitForInit(workspaceId)` - **Never throws**. Returns immediately if
no init needed, init already done, or timeout (5 min)
**`wrapWithInitWait()`** - Wrapper for runtime-dependent tools:
- Wraps: `bash`, `file_read`, `file_edit_insert`,
`file_edit_replace_string`
- Does NOT wrap: `propose_plan`, `todo_*`, `web_search` (execute
immediately)
**Clean separation**: `initStateManager` only passed to wrapper, not to
individual tools. Each tool receives clean config without init-related
fields.
### Before/After
| Before | After |
|--------|-------|
| Tools execute immediately | Runtime tools wait for init first |
| Race conditions on SSH | Init completes before tools run |
| Tools called `waitForInit()` inline | Centralized in
`wrapWithInitWait()` |
| initStateManager in all tool configs | Only in wrapper layer |
## Changes
**Core:**
- `src/services/initStateManager.ts` (362 lines) - Promise-based state
tracking
- `src/services/tools/wrapWithInitWait.ts` (38 lines) - Init wait
wrapper
- `src/utils/tools/tools.ts` - Apply wrapper to runtime tools only
**Tests:**
- `tests/ipcMain/initWorkspace.test.ts` - Extended integration tests (7
tests, all pass)
- `tests/ipcMain/helpers.ts` - New helpers: `collectInitEvents()`,
`waitForInitEnd()`
- Deduplicated ~100 lines of test code
## Integration Test Results
**Verified behavior:**
1. Init hook output streams correctly (not batched)
2. Tools wait for init before executing (both success and failure cases)
3. Workspace remains usable after init failure (tools work, fail
naturally)
4. Init state persists across page reloads
5. Works correctly for both local and SSH runtimes
```
✓ 7 integration tests pass (initWorkspace.test.ts)
✓ 831 unit tests pass
✓ Init wait adds ~5 seconds on first tool use (expected)
✓ Subsequent tools execute immediately (no redundant waiting)
```
## Key Design Decision
**Why `waitForInit()` never throws**: Init hook is optional setup, not a
prerequisite. If init fails or times out, tools should proceed and fail
naturally with their own errors (e.g., "file not found"). This provides
better error messages than blocking on init.
_Generated with `cmux`_
0 commit comments