증상
서버를 재시작하면 대기 중이던 메시지가 복구는 되는데 처리되지 않고 그대로 멈춰 있다.
[queue] recovered 3 persisted message(s) from previous session
...
[slack] ✅ connected as U0BR8UB1AAX
[heartbeat] 0 jobs active
이 뒤로 아무 일도 일어나지 않는다. 3건은 DB의 queued_messages에 남아 있고,
사용자 입장에서는 "대기열에 추가됨"이라고 안내받은 요청이 영영 답을 못 받는다.
원인
createQueueController는 부팅 시 loadPersistedQueue()로 큐를 채우고 로그를 찍지만,
그 뒤에 processQueue()를 부르지 않는다.
// src/agent/spawn/queue.ts
const messageQueue: QueueItem[] = loadPersistedQueue();
if (messageQueue.length > 0) {
console.log(`[queue] recovered ${messageQueue.length} persisted message(s) from previous session`);
}
// ← 여기서 끝. 드레인 트리거가 없다.
processQueue를 부르는 곳은 전부 에이전트 종료 후다 —
lifecycle-handler.ts의 종료 훅, pipeline.ts의 idle 전환, setBusy(false) 전이.
즉 다음 턴이 하나 돌고 끝나야 비로소 밀린 큐가 깨어난다.
실제로 확인했다. 재시작 후 로컬 프롬프트를 하나 넣어주니 그 턴이 끝나면서
복구된 3건이 순서대로 처리돼 각 채널로 답이 나갔다.
왜 문제인가
재시작은 드문 일이 아니다. 설정 변경, 배포, jaw service restart, launchd KeepAlive 재기동.
그때마다 큐에 뭔가 있으면 그건 다음 사람이 말을 걸어줄 때까지 잠든다.
말을 거는 사람이 없으면 영원히 잠든다. 그리고 [queue] recovered N 로그는
"복구했다"고 말하고 있어서, 로그만 보면 정상으로 읽힌다.
제안
부팅 시 큐가 비어있지 않으면 드레인을 트리거한다. 스코프별로 한 번씩:
if (messageQueue.length > 0) {
console.log(`[queue] recovered ...`);
queueMicrotask(() => {
for (const scope of new Set(messageQueue.map(i => normalizeScope(i.scope)))) {
void processQueue(scope);
}
});
}
processQueue는 이미 isSpawnBusy/hasBlockingWorkers/hold를 확인하므로
부팅 직후에 불러도 안전하다. 조건이 안 맞으면 그냥 반환한다.
환경
- cli-jaw v2.17.6 (macOS 15.6, launchd KeepAlive)
- CLI: cursor /
cursor-grok-4.6-high
- 채널: Slack Socket Mode
- 관측: 재시작 후 3건 복구 → 무한 대기 → 로컬 턴 1회 실행 후 정상 배출
증상
서버를 재시작하면 대기 중이던 메시지가 복구는 되는데 처리되지 않고 그대로 멈춰 있다.
이 뒤로 아무 일도 일어나지 않는다. 3건은 DB의
queued_messages에 남아 있고,사용자 입장에서는 "대기열에 추가됨"이라고 안내받은 요청이 영영 답을 못 받는다.
원인
createQueueController는 부팅 시loadPersistedQueue()로 큐를 채우고 로그를 찍지만,그 뒤에
processQueue()를 부르지 않는다.processQueue를 부르는 곳은 전부 에이전트 종료 후다 —lifecycle-handler.ts의 종료 훅,pipeline.ts의 idle 전환,setBusy(false)전이.즉 다음 턴이 하나 돌고 끝나야 비로소 밀린 큐가 깨어난다.
실제로 확인했다. 재시작 후 로컬 프롬프트를 하나 넣어주니 그 턴이 끝나면서
복구된 3건이 순서대로 처리돼 각 채널로 답이 나갔다.
왜 문제인가
재시작은 드문 일이 아니다. 설정 변경, 배포,
jaw service restart, launchd KeepAlive 재기동.그때마다 큐에 뭔가 있으면 그건 다음 사람이 말을 걸어줄 때까지 잠든다.
말을 거는 사람이 없으면 영원히 잠든다. 그리고
[queue] recovered N로그는"복구했다"고 말하고 있어서, 로그만 보면 정상으로 읽힌다.
제안
부팅 시 큐가 비어있지 않으면 드레인을 트리거한다. 스코프별로 한 번씩:
processQueue는 이미isSpawnBusy/hasBlockingWorkers/hold를 확인하므로부팅 직후에 불러도 안전하다. 조건이 안 맞으면 그냥 반환한다.
환경
cursor-grok-4.6-high