mongodb/node-mongodb-native · error · MongoAPIError

Cannot abort a stream that has already completed

Error message

Cannot abort a stream that has already completed

What it means

Thrown by GridFSBucketWriteStream.abort() when the upload stream has already reached its end state (state.streamEnd === true), meaning _final has run or the stream completed/errored. Abort is only meaningful mid-upload to discard partial chunks; once the stream is done, aborting is invalid. Classified as MongoAPIError (TODO NODE-3485 plans a MongoGridFSStreamClosed type).

Source

Thrown at src/gridfs/upload.ts:204

  }

  /** @internal */
  override _final(callback: (error?: Error | null) => void): void {
    if (this.state.streamEnd) {
      return queueMicrotask(callback);
    }
    this.state.streamEnd = true;
    writeRemnant(this, callback);
  }

  /**
   * Places this write stream into an aborted state (all future writes fail)
   * and deletes all chunks that have already been written.
   */
  async abort(): Promise<void> {
    if (this.state.streamEnd) {
      // TODO(NODE-3485): Replace with MongoGridFSStreamClosed
      throw new MongoAPIError('Cannot abort a stream that has already completed');
    }

    if (this.state.aborted) {
      // TODO(NODE-3485): Replace with MongoGridFSStreamClosed
      throw new MongoAPIError('Cannot call abort() on a stream twice');
    }

    this.state.aborted = true;
    const remainingTimeMS = this.timeoutContext?.getRemainingTimeMSOrThrow(
      `Upload timed out after ${this.timeoutContext?.timeoutMS}ms`
    );

    await this.chunks.deleteMany({ files_id: this.id }, { timeoutMS: remainingTimeMS });
  }
}

function handleError(stream: GridFSBucketWriteStream, error: Error, callback: Callback): void {
  if (stream.state.errored) {

View on GitHub (pinned to 3366c21a63)

Solutions

  1. Track upload completion yourself and only call abort while the stream is still writable: if (!stream.destroyed && !stream.writableEnded) await stream.abort();
  2. Move abort() calls to error-handling branches that fire before end() completes.
  3. Guard cleanup with a flag set in the 'finish' and 'error' listeners.

Example fix

// before
stream.end();
await stream.abort(); // throws

// after
let finished = false;
stream.on('finish', () => { finished = true; });
stream.on('error', () => { finished = true; });
stream.end();
if (!finished) await stream.abort();
Defensive patterns

Strategy: validation

Validate before calling

let ended = false;
stream.on('finish', () => { ended = true; });
stream.on('error', () => { ended = true; });
async function safeAbort() {
  if (!ended) await stream.abort();
}

Type guard

function streamEnded(s): boolean {
  return s.destroyed || s.writableEnded;
}

Try / catch

try { await stream.abort(); } catch (e) {
  if (e instanceof MongoAPIError && /already completed/.test(e.message)) return;
  throw e;
}

Prevention

When it happens

Trigger: Awaiting uploadStream.end() (or finishing the stream) and then calling uploadStream.abort(); calling abort inside a 'finish' or 'error' event handler; calling abort after an error already ended the stream.

Common situations: Cleanup code in a finally block that runs abort unconditionally; stream pipelines that call abort on close events; abort invoked from both success and error paths.

Related errors


AI-assisted analysis of mongodb/node-mongodb-native@3366c21a63 (2026-08-04). Data as JSON: /data/errors/222a5a6dd394a296.json. Report an issue: GitHub.