jackwener/OpenCLI · error · CliError
UPLOAD_ERROR
UPLOAD_ERROR
Error message
S3 upload failed: ${getErrorMessage(error)} What it means
uploadPresignedPdf() POSTs a multipart form (presigned fields plus the PDF blob) to the presigned S3 URL. If fetch rejects before a response arrives, it throws UPLOAD_ERROR 'S3 upload failed: <cause>'. This wraps network-level upload failures to the S3 presigned endpoint, separate from S3 returning an HTTP error.
Source
Thrown at clis/paperreview/utils.js:140
}
export function createUploadForm(urlData, pdfFile) {
const form = new FormData();
for (const [key, value] of Object.entries(urlData.presigned_fields ?? {})) {
form.append(key, value);
}
form.append('file', new Blob([new Uint8Array(pdfFile.buffer)], { type: 'application/pdf' }), pdfFile.fileName);
return form;
}
export async function uploadPresignedPdf(presignedUrl, pdfFile, urlData) {
let response;
try {
response = await fetch(presignedUrl, {
method: 'POST',
body: createUploadForm(urlData, pdfFile),
});
}
catch (error) {
throw new CliError('UPLOAD_ERROR', `S3 upload failed: ${getErrorMessage(error)}`, 'Try again in a moment');
}
if (!response.ok) {
const body = await response.text();
throw new CliError('UPLOAD_ERROR', body || `S3 upload failed with status ${response.status}.`, 'Try again in a moment');
}
}
export function summarizeSubmission(options) {
const { pdfFile, email, venue, token, message, s3Key, dryRun = false, status } = options;
return {
status: status ?? (dryRun ? 'dry-run' : 'submitted'),
file: pdfFile.fileName,
file_path: pdfFile.resolvedPath,
size_bytes: pdfFile.sizeBytes,
email,
venue,
token: token ?? '',
review_url: token ? buildReviewUrl(token) : '',
message: message ?? '',View on GitHub (pinned to 49907e53dc)
Solutions
- Retry the upload — the library's own hint is 'Try again in a moment'; presigned flows are typically safe to re-run
- Check connectivity to the bucket host: curl -v <presigned-url-domain>
- If behind a proxy/firewall, allow HTTPS to s3 / *.amazonaws.com or set HTTPS_PROXY
- Regenerate the presigned URL if it may have expired, then retry immediately
Example fix
// before
await uploadPresignedPdf(presignedUrl, pdfFile, urlData); // fails once on flaky network
// after
for (let attempt = 1; attempt <= 3; attempt++) {
try { await uploadPresignedPdf(presignedUrl, pdfFile, urlData); break; }
catch (e) { if (e.code === 'UPLOAD_ERROR' && attempt < 3) await new Promise(r => setTimeout(r, 1000 * attempt)); else throw e; }
} Defensive patterns
Strategy: retry
Validate before calling
async function canReachS3(presignedUrl) {
try { new URL(presignedUrl); }
catch { throw new Error('Malformed presigned URL'); }
const host = new URL(presignedUrl).hostname;
try { await fetch(`https://${host}/`, { method: 'HEAD' }); return true; }
catch { return false; }
} Try / catch
for (let attempt = 1; attempt <= 3; attempt++) {
try { await uploadPresignedPdf(presignedUrl, pdfFile, urlData); break; }
catch (e) {
if (e?.code === 'UPLOAD_ERROR' && e.message.startsWith('S3 upload failed:') && attempt < 3) {
await new Promise(r => setTimeout(r, 1000 * 2 ** (attempt - 1)));
} else throw e;
}
} Prevention
- Upload promptly after obtaining the presigned URL (they expire)
- Retry network-level uploads with exponential backoff
- Allow HTTPS egress to *.amazonaws.com in restricted networks
- Keep PDFs compressed to reduce mid-upload connection drops
When it happens
Trigger: fetch(presignedUrl, { method: 'POST', body: form }) rejecting: no network, DNS failure for the S3/bucket hostname, connection reset mid-upload of a large body, request timeout, TLS error, or FormData/Blob construction failure (e.g. malformed urlData.presigned_fields).
Common situations: Large PDF over a flaky connection dropping mid-upload; corporate proxy blocking PUT/POST to *.amazonaws.com; expired presigned URL causing server-side close; missing NODE_EXTRA_CA_CERTS behind TLS interception.
Related errors
- 抖音封面申请上传地址响应缺少 UploadHost/StoreUri: ${JSON.stringify(applyRe
- 视频上传失败,请检查文件格式和网络连接
- 视频上传/转码超时(${Math.ceil(maxMs / 1000)}秒),请检查网络或稍后重试
- 12306 queryByTrainNo returned HTTP ${resp.status}
- 12306 queryByTrainNo returned non-JSON body
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/94e2237a65f7555d.
Report an issue: GitHub.