jackwener/OpenCLI · error · CommandExecutionError
Video file is empty: ${filePath}
Error message
Video file is empty: ${filePath} What it means
tosUpload rejects empty files early: after confirming existence, if fs.statSync(filePath).size === 0 it throws this CommandExecutionError. TOS multipart upload of zero bytes is invalid, so it fails fast.
Source
Thrown at clis/douyin/_shared/tos-upload.js:273
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}`);
}
const { tos_upload_url: tosUrl, auth, upload_header: uploadHeader, user_id: userId } = uploadInfo;
const parsedTosUrl = new URL(tosUrl);
const region = extractRegionFromHost(parsedTosUrl.host);
const resumePath = getResumeFilePath(filePath);
let resumeState = loadResumeState(resumePath, fileSize);
let uploadId;
let completedParts;
if (resumeState) {
// Resume from previous state
uploadId = resumeState.uploadId;
completedParts = resumeState.parts;
}
else {
// Start fresh
uploadId = await initMultipartUpload(tosUrl, auth, uploadHeader, userId);
completedParts = [];
saveResumeState(resumePath, { uploadId, fileSize, parts: completedParts });View on GitHub (pinned to 49907e53dc)
Solutions
- Regenerate the video file; verify with fs.statSync(p).size > 0 before uploading
- Fix the upstream encode/render step that produced an empty file
- Check disk space and write errors from the producing step
- Add an explicit size precheck with a clear message in your pipeline
Example fix
// before
await tosUpload({ filePath, ... });
// after
const { size } = fs.statSync(filePath);
if (size === 0) { /* regenerate or abort */ }
await tosUpload({ filePath, ... }); Defensive patterns
Strategy: validation
Validate before calling
const { size } = fs.statSync(filePath);
if (size === 0) throw new Error(`refusing to upload empty file: ${filePath}`); Type guard
function isNonEmptyFile(p) {
try { return fs.statSync(p).size > 0; } catch { return false; }
} Try / catch
try { await tosUpload(options); }
catch (e) {
if (String(e.message).startsWith('Video file is empty')) {
await regenerateVideo(options.filePath);
return tosUpload(options);
}
throw e;
} Prevention
- Verify encoder output size > 0 before handing off to upload
- Watch for zero-byte files caused by disk-full or crashed writers
- Delete or quarantine stale placeholder files
- Add pipeline assertions that outputs are non-empty
When it happens
Trigger: Calling tosUpload on a file that exists but is 0 bytes — interrupted encoder output, truncated download, or a file created as a placeholder but never written.
Common situations: Previous render/encode step crashed after touch()ing the output; disk-full left a zero-length file; piping output that produced nothing.
Related errors
- Video file not found: ${filePath}
- 封面文件不存在: ${path.resolve(coverPath)}
- Story media file not found: ${resolved}
- ${label} must reference a non-empty file: ${ref.value}
- not a regular file: ${abs}
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/133d59746e8c7563.
Report an issue: GitHub.