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
- Read the embedded summary to identify which keys failed and with what code.
- For AccessDenied, widen the IAM delete-object permission on the failing prefixes.
- For NoSuchKey, treat as best-effort (idempotent delete) and suppress in cleanup paths.
- 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
- Scope IAM to allow s3:DeleteObject on all keys the service writes.
- Treat NoSuchKey as idempotent success in cleanup paths and suppress it.
- Keep batches small so partial failures are easier to re-drive.
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
- External storage bucket name not configured. Please set `N8N
- Request to S3 failed: ${error.message}
- Azure Blob container name not configured. Please set `N8N_EX
- Request to Azure Blob storage failed: ${error.message}
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/9ec008338cd22311.
Report an issue: GitHub.