Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

Ctrl+C does not stop the dev server: raw mode suppresses SIGINT and close() never exits the process

オープン
#98 コメント 1 件 リアクション 1 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

評価

難易度
4/5
見積もり時間
3〜5日
初心者へのやさしさ
58/100
issue の種類
バグ
明瞭さ
おおむね明確
活発さ
静か
技術スタック
node.js, typescript
領域
cli, devtools

調査の方向性

ShortcutsManager.setup()、#handleKeyPress()、DevServer.close() から始め、node ace serve --watch で問題を再現します。終了ショートカットと watcher/server のクリーンアップを追跡します。Ctrl+C でシェルプロンプトに戻り、孤立した dev-server プロセスが残らなければ完了です。

索引モデルが issue の本文から書いたものです。

説明

Status: Awaiting More Information

Summary

With node ace serve --watch, pressing Ctrl+C does not stop the dev server. The terminal stays wedged and the only way out is closing the terminal tab, which leaves orphaned node ace serve --watch processes re-parented to init (ppid=1).

Crucially, pressing Ctrl+C repeatedly (quickly or slowly) has no effect either, because in this state no signal is ever generated — so there is no default-terminate fallback to rely on.

Steps to reproduce

  1. node ace serve --watch
  2. Wait for watching file system for changes...
  3. Press Ctrl+C

Expected: the dev server shuts down and the shell prompt returns.

Actual: ^C is echoed, nothing else happens. The process keeps running. Closing the terminal tab leaves the server running in the background.

Analysis

ShortcutsManager.setup() puts stdin into raw mode so it can implement the r/c/o/h shortcuts:

setup() {
  if (!process.stdin.isTTY) return;
  process.stdin.setRawMode(true);
  this.#keyPressHandler = (data) => this.#handleKeyPress(data.toString());
  process.stdin.on("data", this.#keyPressHandler);
}

Raw mode clears ISIG, so the terminal no longer translates Ctrl+C into SIGINT. The keypress is instead delivered as a raw byte and handled explicitly:

#handleKeyPress(key) {
  if (key === "\x03" || key === "\x04") return this.#callbacks.onQuit();
  ...
}

onQuit is () => this.close(), and DevServer.close() is:

async close() {
  this.#cleanupKeyboardShortcuts();
  await this.#watcher?.close();
  if (this.#httpServer) {
    this.#httpServer.removeAllListeners();
    this.#httpServer.kill("SIGKILL");
  }
}

close() never calls process.exit() — it relies on the event loop draining naturally. If anything still holds a referenced handle in the parent process (or close() itself does not settle), the process simply never exits. And because raw mode has suppressed SIGINT, the usual safety net — the OS killing the process on a second Ctrl+C — is gone.

Note removeAllListeners() is called on the child immediately before kill("SIGKILL"), which also removes the exit/close listeners that would normally let the parent observe and reap it.

Not fixed by upgrading

I diffed the published builds: ShortcutsManager.setup, #handleKeyPress and DevServer.close() are byte-identical in 8.0.0 (what we run) and 8.4.0 (current latest), so this is not a regression that a version bump resolves.

Workaround

Making stdin a non-TTY causes setup() to return before enabling raw mode, which restores normal terminal signal handling:

// package.json
"dev": "node ace serve --watch < /dev/null"

Ctrl+C then works as expected. The cost is losing the keyboard shortcuts, so we keep the original as a second script for anyone who wants them.

Worth noting for others hitting this: if your app registers its own SIGINT handler (job runners commonly do, to drain in-flight work), you may need to disable that in development too — otherwise the restored signal is trapped by the child instead.

Suggested fix

Guarantee the process actually exits on the quit shortcut, e.g. await this.close() then process.exit(0) in the onQuit path; or restore raw mode and re-raise SIGINT (process.kill(process.pid, 'SIGINT')) so normal signal semantics apply. Either would also make a second Ctrl+C effective as a fallback.

Environment

@adonisjs/assembler 8.0.0 (behaviour confirmed unchanged in 8.4.0)
@adonisjs/core 7.3.1
Node v24.14.0
npm 11.9.0
OS macOS 26.3 (arm64)

Happy to test a patch or open a PR if the suggested direction looks right.

主要言語
TypeScript
スター
40
フォーク
24
平均マージ
2日 11時間
マージ済み PR(30日)
2

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

adonisjs/assembler のほかの issue

adonisjs/assembler の issue をすべて見る

似ている issue

TypeScript の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。