jackwener/OpenCLI · critical · CommandExecutionError
TOS complete multipart upload failed with status ${res.statu
Error message
TOS complete multipart upload failed with status ${res.status}: ${res.body} What it means
completeMultipartUpload finishes the session (phase=finish) and expects HTTP 200 with parsed.code === 2000. This CommandExecutionError is thrown when the server rejects the completion, meaning the video was not finalized on TOS.
Source
Thrown at clis/douyin/_shared/tos-upload.js:255
const body = parts
.sort((a, b) => a.partNumber - b.partNumber)
.map(p => `${p.partNumber}:${p.crc32}`)
.join(',');
const res = await tosRequest({
method: 'POST',
url,
headers: gatewayHeaders(auth, uploadHeader, userId),
body,
});
let parsed;
try {
parsed = JSON.parse(res.body);
}
catch {
parsed = null;
}
if (res.status !== 200 || parsed?.code !== 2000) {
throw new CommandExecutionError(`TOS complete multipart upload failed with status ${res.status}: ${res.body}`, 'Check that all parts were uploaded successfully.');
}
return parsed?.data?.key || null;
}
let _readSyncOverride = null;
/** @internal — for testing only */
export function setReadSyncOverride(fn) {
_readSyncOverride = fn;
}
// ── Public API ───────────────────────────────────────────────────────────────
export async function tosUpload(options) {
const { filePath, uploadInfo, credentials, onProgress } = options;
// Validate file exists
if (!fs.existsSync(filePath)) {
throw new CommandExecutionError(`Video file not found: ${filePath}`, 'Ensure the file path is correct and accessible.');
}
const { size: fileSize } = fs.statSync(filePath);
if (fileSize === 0) {
throw new CommandExecutionError(`Video file is empty: ${filePath}`);View on GitHub (pinned to 49907e53dc)
Solutions
- Ensure every part returned success (check completedParts covers all part numbers) before finishing
- Re-acquire auth and restart the upload if the session is unrecoverable
- Read res.body in the message for the server's specific rejection reason
- Compare local resume state against a fresh init to detect a stale uploadId
Example fix
// before
await completeMultipartUpload(tosUrl, uploadId, parts, auth, uploadHeader, userId);
// after
const expected = Math.ceil(fileSize / PART_SIZE);
if (completedParts.length !== expected) {
throw new Error(`missing parts: have ${completedParts.length}/${expected}`);
}
await completeMultipartUpload(tosUrl, uploadId, completedParts, auth, uploadHeader, userId); Defensive patterns
Strategy: validation
Validate before calling
const expectedParts = Math.ceil(fileSize / PART_SIZE);
if (completedParts.length !== expectedParts) {
throw new Error(`cannot finish: ${completedParts.length}/${expectedParts} parts uploaded`);
} Try / catch
try { const key = await completeMultipartUpload(tosUrl, uploadId, parts, auth, uploadHeader, userId); }
catch (e) {
if (String(e.message).startsWith('TOS complete multipart upload failed')) {
// parts missing server-side: restart with fresh uploadId
return restartUpload(filePath);
}
throw e;
} Prevention
- Verify every part was accepted (count + crc32) before finishing
- Use the resume state file to re-upload missing parts instead of finishing blind
- Re-acquire auth if the upload spans a long time
- Treat a rejected finish as unrecoverable and start a fresh session
When it happens
Trigger: tosUpload -> completeMultipartUpload after all parts; server returns non-200 or code != 2000, typically because some parts are missing/never accepted, or the uploadId was invalidated server-side.
Common situations: A part upload silently failed and the client's completedParts list disagrees with the server; resuming a session whose parts were evicted; expired auth at finish time.
Related errors
- TOS init multipart upload failed with status ${res.status}:
- TOS upload part ${partNumber} failed with status ${res.statu
- Cover image file not found: ${imagePath}
- ImageX upload failed with status ${res.status}: ${body}
- TOS init response missing UploadId: ${res.body}
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/81e6d4d923be4ca3.
Report an issue: GitHub.