Sfoglia il codice sorgente

fix(tui): buffer stdout writes for Terminal.app to prevent CJK crash

Terminal.app crashes (SIGSEGV in CGFontStrikeGetValue) when it sees
partial CJK UTF-8 bytes across multiple write() calls — the font
glyph cache gets corrupted with text bytes in pointer fields.

Buffer all stdout.write() calls within a single event-loop tick and
flush them as one atomic write via process.nextTick. This mirrors
Claude Code's single-write buffering approach (writeDiffToTerminal)
and ensures Terminal.app always receives complete frames.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fanghanjun 7 mesi fa
parent
commit
4e5e44d22d
1 ha cambiato i file con 23 aggiunte e 4 eliminazioni
  1. 23 4
      packages/cli/src/tui/app.ts

+ 23 - 4
packages/cli/src/tui/app.ts

@@ -110,11 +110,30 @@ export async function launchTui(
   projectRoot: string,
   toolsOverride?: InteractionRuntimeTools,
 ): Promise<void> {
-  // Terminal.app has a CoreGraphics bug where UTF-8 bytes (e.g. CJK, em dash)
-  // end up in color space pointers during ANSI color rendering, causing SIGSEGV.
-  // Disable all color output to avoid triggering the crash.
+  // Terminal.app has a CoreGraphics bug: when it sees partial CJK UTF-8 bytes
+  // across multiple write() calls, the font glyph cache gets corrupted (text bytes
+  // end up in pointer fields → SIGSEGV in CGFontStrikeGetValue).
+  // Fix: buffer all writes within a single event-loop tick and flush once,
+  // so Terminal.app always sees complete frames. Same approach as Claude Code's
+  // single-write buffering in writeDiffToTerminal().
   if (isAppleTerminal) {
-    process.env.FORCE_COLOR = "0";
+    let buffer = "";
+    const originalWrite = process.stdout.write.bind(process.stdout);
+    let scheduled = false;
+
+    process.stdout.write = ((chunk: string | Uint8Array): boolean => {
+      buffer += typeof chunk === "string" ? chunk : Buffer.from(chunk).toString();
+      if (!scheduled) {
+        scheduled = true;
+        process.nextTick(() => {
+          scheduled = false;
+          const content = buffer;
+          buffer = "";
+          originalWrite(content);
+        });
+      }
+      return true;
+    }) as typeof process.stdout.write;
   }
 
   projectRoot = await resolveProjectRoot(projectRoot);