Dev server ignores Ctrl+C when stdin was paused before it started (raw mode without resume)
维护者通常 2 天内回复
还没有人认领这个 Issue。
评估
- 难度
- 2/5
- 预计耗时
- 1-3 小时
- 新手友好度
- 82/100
- Issue 类型
- 缺陷
- 描述清晰度
- 描述清楚
- 活跃度
- 活跃
- 技术栈
- node.js, react-native, typescript, webpack
调研方向
Issue将setupInteractions.js和raw模式设置标为切入点。阅读该文件,并追踪在keypress监听器处理Ctrl+C和快捷键r/d/j/a之前,stdin是如何配置的。确认stdin在启动前已暂停时的行为,然后验证更改后快捷键和Ctrl+C可以正常工作。
由索引模型根据 Issue 内容生成。
描述
Describe the bug
When process.stdin has been paused before the dev server starts (which is what a CLI prompt framework's teardown does: input.unpipe() makes Node's Readable.unpipe() call pause() on its source), Ctrl+C does nothing: the server keeps running, Ctrl+Z will not suspend it, and only an external signal stops the process.
setupInteractions switches the terminal to raw mode and then reads Ctrl+C from a keypress listener. Raw mode clears the TTY's ISIG flag, so the kernel stops converting Ctrl+C into a real SIGINT — the 0x03 byte is now deliverable only as stream data, and a paused stream delivers nothing. The keypress listener never fires, so the process.on('SIGINT') handler right next to it is unreachable. Nothing in the start path resumes stdin.
CLI prompt before the bundler starts
|
v
prompt close(): input.unpipe() -> Readable.pause()
v
process.stdin [ PAUSED ]
|
v
setupInteractions()
stdin.setRawMode(true) <-- clears ISIG: kernel SIGINT gone
stdin.on('keypress', ...) <-- never fires: stream paused
process.on('SIGINT', ...) <-- never runs
The same CLI flow works with Metro, which calls process.stdin.resume() immediately after entering raw mode. The r / d / j / a shortcuts are dead in the same session, which is the same paused stream.
Reproduction
Inline, self-contained, no repository needed — repro.mjs in a project with @callstack/repack and react-native installed. It recreates a prompt framework's teardown (pipe stdin, then unpipe) and starts the dev server interactively:
import { Writable } from 'node:stream';
import { createRequire } from 'node:module';
const req = createRequire(import.meta.url);
// what a prompt framework's close() does: pipe stdin, then unpipe it
const sink = new Writable({ write(_c, _e, cb) { cb(); } });
process.stdin.pipe(sink);
await new Promise((r) => setTimeout(r, 200));
process.stdin.unpipe(sink); // <- leaves stdin paused
const commands = (await import('@callstack/repack/commands')).default;
const start = commands.find((c) => c.name === 'start').func;
await start([],
{ root: process.cwd(), reactNativePath: req.resolve('react-native'), platforms: { ios: {}, android: {} } },
{ port: 8081, interactive: true });
Steps to reproduce
node repro.mjsin an interactive terminal.- Wait for
Server listening. - Press Ctrl+C.
All measurements below were taken in a pseudo-terminal with a real Ctrl+C byte injected.
| variant | result |
|---|---|
| as written | hangs; Ctrl+Z and Ctrl+\ also ignored; external SIGTERM kills it |
delete the unpipe() line |
exits in 0.0s |
add process.stdin.resume() before start |
exits in 0.0s, and the r reload shortcut works again |
Reached the same state through real usage as well: any prompt shown before the bundler starts (device selection, port-in-use confirmation) leaves stdin paused, and the dev server then cannot be interrupted.
System Info
System:
OS: macOS 26.5.2
CPU: (10) arm64 Apple M1 Pro
Memory: 439.89 MB / 32.00 GB
Shell:
version: "5.9"
path: /bin/zsh
Binaries:
Node:
version: 26.7.0
path: /opt/homebrew/bin/node
Yarn:
version: 1.22.19
path: /opt/homebrew/bin/yarn
npm:
version: 11.19.0
path: /opt/homebrew/bin/npm
Watchman: Not Found
Managers:
CocoaPods:
version: 1.16.2
path: ~/.rbenv/shims/pod
IDEs:
Xcode:
version: 26.6/17F113
path: /usr/bin/xcodebuild
Languages:
Java:
version: 17.0.9
path: /usr/bin/javac
npmPackages:
"@react-native-community/cli":
installed: 20.1.0
wanted: 20.1.0
react:
installed: 19.2.3
wanted: 19.2.3
react-native:
installed: 0.86.0
wanted: 0.86.0
Android:
hermesEnabled: true
newArchEnabled: true
iOS:
hermesEnabled: true
newArchEnabled: false
Tested on macOS with the rspack engine. The webpack engine uses the same setupInteractions file, so it should behave identically; Linux and Windows untested.
Re.Pack Version
5.4.1 — and setupInteractions.js is byte-identical in 5.4.2 and the current canary, so upgrading does not help.
Proposed fix
One line, where raw mode is entered:
readline.emitKeypressEvents(process.stdin);
process.stdin.setRawMode(true);
+ // Raw mode clears the TTY's ISIG flag, so Ctrl+C reaches us only as stream
+ // data. A prior unpipe() (e.g. from a CLI prompt framework) leaves the stream
+ // paused, which would silently swallow the keypress.
+ process.stdin.resume();
resume() rather than an additional SIGINT listener: raw mode is what removed the kernel signal, and a listener registered on a paused stream is equally unreachable. The caller that pauses stdin is not at fault — unpipe() pausing its source is documented Node behaviour, and every consumer that later takes over raw-mode input has to resume it.
Also worth hardening on the calling side: a CLI that starts the dev server in-process after showing its own prompts should resume stdin before handing over, so the failure cannot be reintroduced by a bundler plugin.
Context from the consuming project: https://github.com/Expensify/App/issues/105422
- 主要语言
- TypeScript
- 星标
- 1.9k
- 派生
- 177
- 平均合并
- 2 天 10 小时
- 30 天内合并 PR
- 23
环境准备
- 没有 Dockerfile 或 Docker Compose 文件
- 没有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
callstack/repack 的其他 Issue
-
status:new type:feature
难度 3/5 1-2 天 新手友好度 50/100
维护者通常 2 天内回复
-
ScriptManager caches the rejected promise when a resolver throws, so loadScript/prefetchScript never retry可能已有人在做 @linhh-phv 于 16 天前认领。 未关闭
难度 2/5 1-3 小时 新手友好度 35/100
维护者通常 2 天内回复
-
Inlined assets with fractional scales resolve to the wrong variant可能已有人在做 @RaphaelFakhri 于 16 天前认领。 未关闭
难度 2/5 1-3 小时 新手友好度 25/100
维护者通常 2 天内回复
-
json as string in JS bundle可能已有人在做 关联的 PR 仍在进行中或已合并。 未关闭
难度 5/5 一周以上 新手友好度 35/100
callstack/repack#1461 · 1 条评论 ·
维护者通常 2 天内回复
-
area:repack type:feature
难度 5/5 一周以上 新手友好度 42/100
callstack/repack#1423 · 1 条评论 ·
维护者通常 2 天内回复
相似的 Issue
-
难度 2/5 1-3 小时 新手友好度 72/100
维护者通常 3 天内回复
-
难度 2/5 1-3 小时 新手友好度 66/100
维护者通常 2 天内回复
-
reproduced
难度 2/5 1-3 小时 新手友好度 74/100
anomalyco/opencode#62052 · 1 条评论 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 68/100
strapi/design-system#2084 ·
维护者通常 3 天内回复
-
难度 1/5 1 小时以内 新手友好度 88/100
维护者通常 1 天内回复