Skip to content

Conversation

@Xy2002
Copy link

@Xy2002 Xy2002 commented Dec 24, 2025

This PR fixes an issue where the CLI output would be truncated when piped to other processes. This was happening because process.exit() was being called immediately after console.log() or process.stdout.write(), without waiting for the internal buffer to drain.

fix #565

@kamusis
Copy link
Contributor

kamusis commented Feb 5, 2026

Hi, I noticed this PR successfully addresses the truncation issue described in #565, but it hasn't been merged for a while. I suspect the hesitation might be due to the architectural impact of turning all output-related methods into async functions throughout the codebase, which creates a lot of "async infection."

As an alternative, what if we implement a "drain-on-exit" pattern instead? This intercept-at-the-exit approach is much less intrusive while still solving the issue reliably.

Suggested Alternative:

export function safeExit(code = 0) {
  const drainStdout = process.stdout.write('');
  const drainStderr = process.stderr.write('');

  if (!drainStdout || !drainStderr) {
    let drainedCount = 0;
    const targetCount = (drainStdout ? 0 : 1) + (drainStderr ? 0 : 1);

    const onDrain = () => {
      drainedCount++;
      if (drainedCount >= targetCount) process.exit(code);
    };

    if (!drainStdout) process.stdout.once('drain', onDrain);
    if (!drainStderr) process.stderr.once('drain', onDrain);

    // Force exit after a short timeout if drain doesn't happen
    setTimeout(() => process.exit(code), 1000).unref();
  } else {
    process.exit(code);
  }
}

Pros & Cons:

  • Pros: Extremely non-intrusive. No need to refactor the core business logic or loop. It acts as a global safety net for all output (including console.log from any module).
  • Cons: It doesn't handle backpressure during the command execution if you are streaming gigabytes of data. However, since the CLI's main output is AI responses (typically in the KB to MB range), this limitation is practically negligible in this context.

What do you think? This might be a cleaner path to finally resolving the truncation bug without refactoring the whole message flow.

@Xy2002 Xy2002 closed this Feb 6, 2026
@Xy2002 Xy2002 force-pushed the fix/cli-output-truncation branch from 5128119 to 96ae3d9 Compare February 6, 2026 05:54
@Xy2002 Xy2002 reopened this Feb 6, 2026
@Xy2002
Copy link
Author

Xy2002 commented Feb 6, 2026

Hi, I noticed this PR successfully addresses the truncation issue described in #565, but it hasn't been merged for a while. I suspect the hesitation might be due to the architectural impact of turning all output-related methods into async functions throughout the codebase, which creates a lot of "async infection."

As an alternative, what if we implement a "drain-on-exit" pattern instead? This intercept-at-the-exit approach is much less intrusive while still solving the issue reliably.

Suggested Alternative:

export function safeExit(code = 0) {
  const drainStdout = process.stdout.write('');
  const drainStderr = process.stderr.write('');

  if (!drainStdout || !drainStderr) {
    let drainedCount = 0;
    const targetCount = (drainStdout ? 0 : 1) + (drainStderr ? 0 : 1);

    const onDrain = () => {
      drainedCount++;
      if (drainedCount >= targetCount) process.exit(code);
    };

    if (!drainStdout) process.stdout.once('drain', onDrain);
    if (!drainStderr) process.stderr.once('drain', onDrain);

    // Force exit after a short timeout if drain doesn't happen
    setTimeout(() => process.exit(code), 1000).unref();
  } else {
    process.exit(code);
  }
}

Pros & Cons:

  • Pros: Extremely non-intrusive. No need to refactor the core business logic or loop. It acts as a global safety net for all output (including console.log from any module).
  • Cons: It doesn't handle backpressure during the command execution if you are streaming gigabytes of data. However, since the CLI's main output is AI responses (typically in the KB to MB range), this limitation is practically negligible in this context.

What do you think? This might be a cleaner path to finally resolving the truncation bug without refactoring the whole message flow.

Thanks for the suggestion — I agree the async‑infection was the main architectural downside of my original approach. A “drain‑on‑exit” guard is a cleaner, low‑intrusion fix for this class of truncation.

I’ve implemented a minimal variant of this in runQuiet: instead of making output methods async, we wait for stdout/stderr to flush right before exit. This keeps the rest of the codebase synchronous and avoids refactoring the message flow.

One small tweak to your proposal: I prefer the explicit write('', cb) drain instead of relying on drain events only, because it’s deterministic even when write() returns true.

So, I think this is the right direction, and I’m happy to align with that approach rather than async‑ifying output throughout the codebase.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: CLI Output Truncated to 8192 Bytes when use Headless Mode

2 participants