Conversation
The process was hanging on Ctrl+C because: 1. The rate-limiter's setInterval kept the event loop alive (no .unref()) 2. No signal handlers existed to stop active Claude queries or disconnect Prisma Fixes: - Add .unref() to the rate-limiter cleanup interval - Add SIGINT/SIGTERM handlers that stop all active Claude queries and disconnect Prisma before exiting - A second Ctrl+C force-exits immediately Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request introduces graceful shutdown handling by registering SIGINT and SIGTERM listeners to stop active Claude sessions and disconnect Prisma. It also applies .unref() to the rate limiter's cleanup interval to prevent it from hanging the process. A review comment highlights that the registration logic should be idempotent to prevent multiple listeners from being attached during Next.js development.
| } | ||
|
|
||
| // Register graceful shutdown handler | ||
| registerShutdownHandler(); |
There was a problem hiding this comment.
In Next.js, the register function in instrumentation.ts can be executed multiple times during development (e.g., due to Hot Module Replacement). This would result in multiple SIGINT/SIGTERM listeners being attached to the process, leading to redundant cleanup operations and potential race conditions during shutdown. It is recommended to ensure the signal handlers are only registered once by using a global flag, ensuring that initialization logic is idempotent and protected against concurrent or repeated execution.
if (!(globalThis as any)._shutdownHandlerRegistered) {
registerShutdownHandler();
(globalThis as any)._shutdownHandlerRegistered = true;
}References
- To prevent race conditions in methods with asynchronous initialization, set a processing/locked flag synchronously at the start of the method, before any await calls, to ensure concurrent invocations are rejected immediately.
If stopAllSessions() or prisma.$disconnect() hangs, force exit after 10 seconds. This is especially important for SIGTERM from systemd where there's no second signal to trigger force-exit. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
setIntervalin rate-limiter keeping the Node.js event loop alive by adding.unref()stopAllSessions()to claude-runner for bulk session cleanup during shutdownRoot Causes
The process was hanging on Ctrl+C because:
setInterval— the hourly cleanup timer (line 216 ofrate-limiter.ts) was never.unref()'d, so it kept the event loop alive indefinitelyTest plan
pnpm test:run— 361 tests)pnpm test:integration— 143 tests)pnpm start, then Ctrl+C — should exit promptly🤖 Generated with Claude Code