jackwener/OpenCLI · error · ArgumentError

weixin create-draft cover-image is not a file: ${absPath}

Error message

weixin create-draft cover-image is not a file: ${absPath}

What it means

The path exists but stat.isFile() is false — the cover image points to a directory or special file. The library rejects it with this ArgumentError since the WeChat draft API needs an actual image file to upload.

Source

Thrown at clis/weixin/create-draft.js:44

}

function isRecoverableFileInputError(error) {
    const message = error instanceof Error ? error.message : String(error);
    return /unknown action|not supported|not[-\s]?allowed|notallowederror/i.test(message);
}

function resolveCoverImage(rawPath) {
    const value = String(rawPath ?? '').trim();
    if (!value) throw new ArgumentError('weixin create-draft cover-image cannot be empty');
    const absPath = path.resolve(value);
    let stat;
    try {
        stat = fs.statSync(absPath);
    } catch {
        throw new ArgumentError(`weixin create-draft cover-image does not exist: ${absPath}`);
    }
    if (!stat.isFile()) {
        throw new ArgumentError(`weixin create-draft cover-image is not a file: ${absPath}`);
    }
    const extension = path.extname(absPath).toLowerCase();
    const mimeType = IMAGE_MIME_TYPES.get(extension);
    if (!mimeType) {
        throw new ArgumentError('weixin create-draft cover-image must be JPEG, PNG, GIF, or WebP');
    }
    return { absPath, fileName: path.basename(absPath), mimeType };
}

async function navigateToEditor(page) {
    await page.goto(WEIXIN_HOME);
    await page.wait(3);
    const token = await evaluate(page, `(window.location.href.match(/token=(\\d+)/)||[])[1]`);
    if (!token) {
        throw new AuthRequiredError(WEIXIN_DOMAIN, 'Please log in to the WeChat Official Account platform and retry.');
    }
    await page.goto(`https://mp.weixin.qq.com/cgi-bin/appmsg?t=media/appmsg_edit_v2&action=edit&isNew=1&type=77&token=${token}&lang=zh_CN`);
    await page.wait(4);

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Point cover-image at an actual image file, not a directory.
  2. If using a symlink, confirm it resolves to a regular file.
  3. Print/inspect the resolved path in the error to see what was actually passed.

Example fix

// before
coverImage: './assets'
// after
coverImage: './assets/cover.jpg'
Defensive patterns

Strategy: validation

Validate before calling

const st = fs.statSync(path.resolve(coverImage));
if (!st.isFile()) throw new Error('cover-image must be a regular file, not a directory');

Type guard

function isRegularFile(p) {
  try { return fs.statSync(p).isFile(); } catch { return false; }
}

Try / catch

try {
  await createDraft({ coverImage });
} catch (err) {
  if (err instanceof ArgumentError && err.message.includes('is not a file')) {
    console.error('coverImage must point to a file, not a directory');
  } else throw err;
}

Prevention

When it happens

Trigger: Passing a directory path as cover-image; passing a device/fifo/socket path; on some setups a symlink pointing at a directory (statSync follows symlinks).

Common situations: Pointing cover-image at an assets folder instead of a file; shell glob expanding oddly; variable holding a directory from upstream config.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


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