{"record":{"id":"5c4b500d5111993c","repo":"gildas-lormeau/SingleFile","slug":"error-message-s3","errorCode":null,"errorMessage":"error.message + \" (S3)\"","messagePattern":"error\\.message \\+ \" \\(S3\\)\"","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/core/bg/downloads.js","lineNumber":495,"sourceCode":"\t\t\tconst client = new GitHub(githubToken, githubUser, githubRepository, githubBranch);\n\t\t\tbusiness.setCancelCallback(taskId, () => client.abort());\n\t\t\treturn await client.upload(filename, content, { filenameConflictAction, prompt });\n\t\t}\n\t} catch (error) {\n\t\tthrow new Error(error.message + \" (GitHub)\", { cause: error });\n\t}\n}\n\nasync function saveToS3(taskId, filename, blob, domain, region, bucket, accessKey, secretKey, { filenameConflictAction, prompt }) {\n\ttry {\n\t\tconst taskInfo = business.getTaskInfo(taskId);\n\t\tif (!taskInfo || !taskInfo.cancelled) {\n\t\t\tconst client = new S3(region, bucket, accessKey, secretKey, domain);\n\t\t\tbusiness.setCancelCallback(taskId, () => client.abort());\n\t\t\treturn await client.upload(filename, blob, { filenameConflictAction, prompt });\n\t\t}\n\t} catch (error) {\n\t\tthrow new Error(error.message + \" (S3)\", { cause: error });\n\t}\n}\n\nasync function saveWithWebDAV(taskId, filename, content, url, username, password, { filenameConflictAction, prompt }) {\n\ttry {\n\t\tconst taskInfo = business.getTaskInfo(taskId);\n\t\tif (!taskInfo || !taskInfo.cancelled) {\n\t\t\tconst client = new WebDAV(url, username, password);\n\t\t\tbusiness.setCancelCallback(taskId, () => client.abort());\n\t\t\treturn await client.upload(filename, content, { filenameConflictAction, prompt });\n\t\t}\n\t} catch (error) {\n\t\tthrow new Error(error.message + \" (WebDAV)\", { cause: error });\n\t}\n}\n\nasync function saveWithMCP(taskId, filename, content, serverUrl, authToken, { filenameConflictAction, prompt }) {\n\ttry {","sourceCodeStart":477,"sourceCodeEnd":513,"githubUrl":"https://github.com/gildas-lormeau/SingleFile/blob/517fb7c5cf2096d89933b747e862d8ecf616a9f9/src/core/bg/downloads.js#L477-L513","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Check error.cause and verify accessKey/secretKey are correct and active","Verify bucket name and region match (e.g. us-east-1) and the bucket exists","Confirm the custom domain/endpoint URL is correct and reachable","Ensure the credentials' policy allows s3:PutObject on the bucket"],"exampleFix":"// before\nnew S3('us-west-1', 'my-bucket', key, secret, domain); // bucket is in us-east-1\n// after\nnew S3('us-east-1', 'my-bucket', key, secret, domain);","handlingStrategy":"validation","validationCode":"if (!region || !bucket || !accessKey || !secretKey) {\n  throw new Error('Missing S3 save configuration');\n}\nif (!/^s3[.-][a-z0-9-]+\\.amazonaws\\.com$/.test(domain || 's3.amazonaws.com') && !customEndpointOk) {\n  console.warn('Custom S3 domain, verify reachability');\n}","typeGuard":"function hasS3Config(o) {\n  return typeof o.region === 'string' && typeof o.bucket === 'string' &&\n    typeof o.accessKey === 'string' && o.accessKey.length > 0 &&\n    typeof o.secretKey === 'string' && o.secretKey.length > 0;\n}","tryCatchPattern":"try {\n  await saveToS3(taskId, filename, blob, s3Opts);\n} catch (error) {\n  if (error.message.endsWith('(S3)')) {\n    const root = error.cause;\n    if (String(root).includes('403')) { /* fix credentials/policy */ }\n  } else { throw error; }\n}","preventionTips":["Test S3 credentials with a HEAD bucket call during setup","Keep bucket name and region consistent","Prefer environment/secret storage for keys; rotate before expiry","Check clock sync if signatures fail intermittently"],"tags":["s3","upload","error-wrapping","aws"],"backgroundTag":"s3-upload-failed","analyzedSha":"517fb7c5cf2096d89933b747e862d8ecf616a9f9","analyzedAt":"2026-09-01T10:05:25.770Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}