fix: make command loading resilient to MCP/Skill failures
Merged
AN
7mo ago+456+456+012345678901234567890123456789-47-47-01234567890123456789
Summary
- Wrap MCP prompt loading and Skill loading in individual
try/catchblocks incommand/index.tsso that default commands (init, discover, review) are always served even when plugins fail - Add
safe()error wrapper around each non-blocking sync request insync.tsxso one failing request doesn't silently prevent all others (including commands) from populating the TUI store - Add 22 tests covering command resilience, default command properties, and the safe wrapper pattern
Root Cause
When MCP.prompts() or Skill.all() threw during command state initialization, the entire state promise rejected ā all default commands including /discover disappeared from the TUI. Additionally, the non-blocking Promise.all in sync.tsx was fire-and-forget with no error handler, so failures were silently swallowed and setStore("status", "complete") never ran.
Changes
| File | Change |
|---|---|
src/command/index.ts | Wrap MCP and Skill loading in try/catch |
src/cli/cmd/tui/context/sync.tsx | Add safe() wrapper for individual error handling |
test/command/command-resilience.test.ts | 15 tests for command defaults, properties, and resilience pattern |
test/command/sync-resilience.test.ts | 7 tests for the safe() wrapper pattern |
Test plan
- All 1388 existing tests pass
- 22 new tests pass covering:
- Default commands (init, discover, review) always present
- Discover command has correct metadata and template
- Resilience pattern: MCP failure, Skill failure, both failures
- Skills can't overwrite default commands
- safe() wrapper: individual failures, all failures, non-Error rejections
- Contrasts old behavior (Promise.all rejects) vs new (always resolves)
š¤ Generated with Claude Code