AW-4: Refactor ClaudeInvocationService — enqueue to BullMQ instead of spawning#
Problem
ClaudeInvocationService.invoke() was tightly coupled to the NestJS process via child_process.spawn(), blocking the event loop and preventing distributed execution of Claude invocations.
Task / Link
Closes #440
Changes
- Create
InvocationResultListener—@Processor('phase-result')BullMQ worker that resolves pending promises by jobId; handles unmatched jobIds (warn + return) and timeouts (reject with message) - Refactor
ClaudeInvocationService.invoke()— remove allspawn/stream logic; enqueue job tophase-invokewith full payload (jobId,taskId,phase,skill,env,cwd,timeout,args); await result viawaitForResult(jobId, timeoutMs); parse signal from{TASK_DIR}/meta/ai-output.jsonlwritten by the worker - Update
InvokeModule— registerBullModule.forRootAsync()(inline stub, removable after AW-3 #439 merges),phase-invokeandphase-resultqueues, andInvocationResultListenerprovider - Update
claude-invocation.service.spec.ts— replacechild_processspawn mocks with BullMQ queue mocks; add tests for timeout propagation and missingai-output.jsonl; 28 tests all green
Notes
⚠️ BullModule.forRootAsync() is registered inline in InvokeModule as a stub (marked with TODO comment). Once AW-3 (#439) merges and BullModule is registered in AppModule, remove the stub from InvokeModule.
⚠️ This PR installs @nestjs/bullmq, bullmq, and ioredis into package.json — those will also be added by AW-3, so a dedup/cleanup will be needed when both land.
Testing
- Unit tests: 793 passing (48 suites), including 28 new/updated tests in
claude-invocation.service.spec.ts - Lint: zero warnings/errors