gildas-lormeau/SingleFile · error · Error

error.message + " (S3)"

Error message

error.message + " (S3)"

What it means

saveToS3 wraps any error from the S3 client upload (client.upload on a new S3(region, bucket, accessKey, secretKey, domain)) and rethrows it with an " (S3)" suffix plus the original as cause. This marks the failure as belonging to the S3 save destination.

Source

Thrown at src/core/bg/downloads.js:495

			const client = new GitHub(githubToken, githubUser, githubRepository, githubBranch);
			business.setCancelCallback(taskId, () => client.abort());
			return await client.upload(filename, content, { filenameConflictAction, prompt });
		}
	} catch (error) {
		throw new Error(error.message + " (GitHub)", { cause: error });
	}
}

async function saveToS3(taskId, filename, blob, domain, region, bucket, accessKey, secretKey, { filenameConflictAction, prompt }) {
	try {
		const taskInfo = business.getTaskInfo(taskId);
		if (!taskInfo || !taskInfo.cancelled) {
			const client = new S3(region, bucket, accessKey, secretKey, domain);
			business.setCancelCallback(taskId, () => client.abort());
			return await client.upload(filename, blob, { filenameConflictAction, prompt });
		}
	} catch (error) {
		throw new Error(error.message + " (S3)", { cause: error });
	}
}

async function saveWithWebDAV(taskId, filename, content, url, username, password, { filenameConflictAction, prompt }) {
	try {
		const taskInfo = business.getTaskInfo(taskId);
		if (!taskInfo || !taskInfo.cancelled) {
			const client = new WebDAV(url, username, password);
			business.setCancelCallback(taskId, () => client.abort());
			return await client.upload(filename, content, { filenameConflictAction, prompt });
		}
	} catch (error) {
		throw new Error(error.message + " (WebDAV)", { cause: error });
	}
}

async function saveWithMCP(taskId, filename, content, serverUrl, authToken, { filenameConflictAction, prompt }) {
	try {

View on GitHub (pinned to 517fb7c5cf)

Solutions

  1. Check error.cause and verify accessKey/secretKey are correct and active
  2. Verify bucket name and region match (e.g. us-east-1) and the bucket exists
  3. Confirm the custom domain/endpoint URL is correct and reachable
  4. Ensure the credentials' policy allows s3:PutObject on the bucket

Example fix

// before
new S3('us-west-1', 'my-bucket', key, secret, domain); // bucket is in us-east-1
// after
new S3('us-east-1', 'my-bucket', key, secret, domain);
Defensive patterns

Strategy: validation

Validate before calling

if (!region || !bucket || !accessKey || !secretKey) {
  throw new Error('Missing S3 save configuration');
}
if (!/^s3[.-][a-z0-9-]+\.amazonaws\.com$/.test(domain || 's3.amazonaws.com') && !customEndpointOk) {
  console.warn('Custom S3 domain, verify reachability');
}

Type guard

function hasS3Config(o) {
  return typeof o.region === 'string' && typeof o.bucket === 'string' &&
    typeof o.accessKey === 'string' && o.accessKey.length > 0 &&
    typeof o.secretKey === 'string' && o.secretKey.length > 0;
}

Try / catch

try {
  await saveToS3(taskId, filename, blob, s3Opts);
} catch (error) {
  if (error.message.endsWith('(S3)')) {
    const root = error.cause;
    if (String(root).includes('403')) { /* fix credentials/policy */ }
  } else { throw error; }
}

Prevention

When it happens

Trigger: Any rejection inside S3.upload(filename, blob, {filenameConflictAction, prompt}) called from downloadContent/downloadCompressedContent: bad credentials, wrong region/bucket, endpoint/domain unreachable, network error, or abort.

Common situations: Wrong accessKey/secretKey; bucket name typo or bucket in a different region; custom S3-compatible endpoint (domain) misconfigured (e.g. MinIO, Wasabi); clock skew causing signature errors; bucket policy denying PutObject.

Related errors


AI-assisted analysis of gildas-lormeau/SingleFile@517fb7c5cf (2026-09-01). Data as JSON: /api/errors/5c4b500d5111993c. Report an issue: GitHub.