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
- Track upload completion yourself and only call abort while the stream is still writable: if (!stream.destroyed && !stream.writableEnded) await stream.abort();
- Move abort() calls to error-handling branches that fire before end() completes.
- 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
- Track stream completion in finish/error handlers
- Never call abort from inside finish/error listeners
- Centralize abort into a single guarded function
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
- Cannot call abort() on a stream twice
- Options cannot be changed after the stream is initialized
- Stream start (${options.start}) must not be more than the le
- Stream start (${options.start}) must not be negative
- Stream start (${options.start}) must not be greater than str
AI-assisted analysis of mongodb/node-mongodb-native@3366c21a63 (2026-08-04).
Data as JSON: /data/errors/222a5a6dd394a296.json.
Report an issue: GitHub.