n8n-io/n8n · error · UnexpectedError

Failed to delete ${errors.length} of ${batch.length} objects

Error message

Failed to delete ${errors.length} of ${batch.length} objects: ${summary}

What it means

UnexpectedError thrown inside the S3 batch delete after DeleteObjectsCommand returns an Errors[] array. S3's bulk delete reports per-key failures inside a 200 response rather than failing the HTTP call, so the code inspects Errors and throws when any key failed. The message reports counts plus the first MAX_REPORTED_DELETE_ERRORS (5) failures with key/code/message.

Source

Thrown at packages/@n8n/blob-storage/src/object-store/object-store.service.ee.ts:301

				this.logger.debug('Sending DELETE MANY request to S3', {
					bucket: this.bucket,
					objectCount: batch.length,
				});

				// `DeleteObjects` reports per-key failures in the response rather than failing the request
				const { Errors: errors } = await this.s3Client.send(new DeleteObjectsCommand(params));

				if (errors && errors.length > 0) {
					this.logger.error('Failed to delete objects from S3', {
						bucket: this.bucket,
						failures: errors.map((e) => ({ key: e.Key, code: e.Code, message: e.Message })),
					});

					const summary = errors
						.slice(0, MAX_REPORTED_DELETE_ERRORS)
						.map((e) => `${e.Key ?? '<unknown key>'} (${e.Code ?? '?'}: ${e.Message ?? '?'})`)
						.join(', ');
					throw new UnexpectedError(
						`Failed to delete ${errors.length} of ${batch.length} objects: ${summary}`,
					);
				}
			}
		} catch (e) {
			this.handleS3Error(e);
		}
	}

	/**
	 * List objects with a common prefix in the configured bucket.
	 */
	async list(prefix: string) {
		const items = [];
		let isTruncated = true;
		let continuationToken;

		try {

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Read the embedded summary to identify which keys failed and with what code.
  2. For AccessDenied, widen the IAM delete-object permission on the failing prefixes.
  3. For NoSuchKey, treat as best-effort (idempotent delete) and suppress in cleanup paths.
  4. Re-run the delete for the remaining failing keys after fixing permissions.
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-check IAM delete permission on a representative key before bulk delete
// (operational check, not code in-process)

Type guard

import { UnexpectedError } from 'n8n-workflow';
const isPartialDeleteFailure = (e: unknown): boolean =>
  e instanceof UnexpectedError && /Failed to delete \d+ of \d+ objects/.test(e.message);

Try / catch

try {
  await objectStoreService.deleteBatch(keys);
} catch (e) {
  if (e instanceof UnexpectedError && /Failed to delete/.test(e.message)) {
    // parse the summary, retry only keys not marked NoSuchKey, fix IAM for AccessDenied
  }
  throw e;
}

Prevention

When it happens

Trigger: s3Client.send(new DeleteObjectsCommand({ Delete: { Objects: [...] } })) resolves with response.Errors non-empty. Common per-key codes: AccessDenied, NoSuchKey, or internal error on individual objects within the batch.

Common situations: Batch cleanup of execution data where some objects are already gone (NoSuchKey) or locked (Object Lock / WORM); an IAM policy that allows list but not delete on some keys; cross-account bucket where some prefixes are denied; S3 inventory lag.

Related errors


AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12). Data as JSON: /api/errors/9ec008338cd22311. Report an issue: GitHub.