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

  1. Regenerate the video file; verify with fs.statSync(p).size > 0 before uploading
  2. Fix the upstream encode/render step that produced an empty file
  3. Check disk space and write errors from the producing step
  4. 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

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


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/133d59746e8c7563. Report an issue: GitHub.