diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index a34da9d160e..8b09a2903bd 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -1,4 +1,12 @@ +## 15.8.3 + +_Released 1/13/2026 (PENDING)_ + +**Bugfixes:** + +- Fixed an issue where output could be cut off when the process exits in CI environments. Fixed in [#33213](https://github.com/cypress-io/cypress/pull/33213). + ## 15.8.2 _Released 01/06/2026_ diff --git a/packages/server/lib/cypress.ts b/packages/server/lib/cypress.ts index f82b8164642..3f6584da658 100644 --- a/packages/server/lib/cypress.ts +++ b/packages/server/lib/cypress.ts @@ -20,6 +20,24 @@ const debug = Debug('cypress:server:cypress') type Mode = 'exit' | 'info' | 'interactive' | 'pkg' | 'record' | 'results' | 'run' | 'smokeTest' | 'version' | 'returnPkg' | 'exitWithCode' +/** + * Waits for a writable stream to drain its buffer. + * This is necessary because when stdout/stderr is piped (e.g., in CI environments), + * writes are buffered and process.exit() would terminate before the buffer is flushed. + */ +const waitForStreamDrain = (stream: NodeJS.WriteStream): Promise => { + return new Promise((resolve) => { + if (!stream.isTTY && stream.writableLength > 0) { + debug('waiting for stream to drain, writableLength: %d', stream.writableLength) + stream.once('drain', resolve) + // Safety timeout to prevent hanging indefinitely + setTimeout(resolve, 5000) + } else { + setImmediate(resolve) + } + }) +} + const exit = async (code = 0) => { // TODO: we shouldn't have to do this // but cannot figure out how null is @@ -41,6 +59,13 @@ const exit = async (code = 0) => { debug('telemetry shutdown errored with: ', err) }) + // Wait for stdout/stderr to drain before exiting to prevent truncated output in CI + debug('waiting for stdout/stderr to drain before exit') + await Promise.all([ + waitForStreamDrain(process.stdout), + waitForStreamDrain(process.stderr), + ]) + debug('process.exit', code) return process.exit(code)