jackwener/OpenCLI · error · ArgumentError
not a regular file: ${abs}
Error message
not a regular file: ${abs} What it means
This ArgumentError is thrown by `attachment-upload` in clis/slock/attachment-upload.js:57 when `stat.isFile()` is false — the path exists and is statable, but is not a regular file (a directory, FIFO, socket, device node, etc.). The upload flow reads the whole file into memory and base64-encodes it, which only makes sense for regular files, so non-file paths are rejected early.
Source
Thrown at clis/slock/attachment-upload.js:57
strategy: Strategy.COOKIE,
browser: true,
siteSession: 'persistent',
args: [
{ name: 'file', positional: true, required: true, help: 'Local file path to upload (single file; max 50 MB)' },
{ name: 'channel', positional: true, required: true, help: 'channelId UUID or #name — server requires the attachment be scoped to a channel' },
{ name: 'server', help: 'Override active server slug' },
],
columns: ['attachmentId', 'filename', 'mimeType', 'sizeBytes'],
func: async (page, kwargs) => {
const filePath = String(kwargs.file ?? '').trim();
if (!filePath) throw new ArgumentError('file path required');
const channel = String(kwargs.channel ?? '').trim();
if (!channel) throw new ArgumentError('channel required (UUID or #name); server rejects uploads without channelId');
const abs = path.resolve(filePath);
let stat;
try { stat = fs.statSync(abs); }
catch (e) { throw new ArgumentError(`file not readable: ${abs} (${e.message})`); }
if (!stat.isFile()) throw new ArgumentError(`not a regular file: ${abs}`);
if (stat.size === 0) throw new ArgumentError(`file is empty: ${abs}`);
if (stat.size > MAX_BYTES) {
throw new ArgumentError(`file is ${stat.size} bytes, exceeds server limit ${MAX_BYTES} (50 MiB). Split or compress before upload.`);
}
const buf = fs.readFileSync(abs);
const filename = path.basename(abs);
const b64 = buf.toString('base64');
await page.goto(SLOCK_HOME_URL);
const snippet = `
${authHeadersFragment({ serverScoped: true, serverIdOverride: kwargs.server })}
${channelResolveFragment(channel)}
// multipart wants the browser to set its own boundary — strip content-type.
const uploadHeaders = { authorization: headers.authorization, accept: headers.accept };
if (headers['x-server-id']) uploadHeaders['x-server-id'] = headers['x-server-id'];
// Rebuild File from base64 → Uint8Array → Blob → File. The base64 stringView on GitHub (pinned to 49907e53dc)
Solutions
- Pass the path to a specific regular file, not a directory: `attachment-upload ./attachments/log.zip '#general'`.
- If you meant to upload multiple files, invoke the command once per file (the endpoint caps at 5 files per call and this CLI uploads one at a time).
- Check what the path actually is with `file <path>` or `stat -c %F <path>` before uploading in scripts.
Example fix
// before: directory passed instead of a file attachment-upload ./build-output '#general' // after: upload a specific file within it attachment-upload ./build-output/bundle.zip '#general'
Defensive patterns
Strategy: validation
Validate before calling
import fs from 'node:fs';
const st = fs.statSync(abs);
if (!st.isFile()) {
throw new Error(`${abs} is not a regular file (use a specific file, not a directory)`);
} Try / catch
try {
await upload({ file: abs, channel });
} catch (e) {
if (e instanceof ArgumentError && e.message.startsWith('not a regular file')) {
console.error(`${abs} is a directory or special file — pass an individual file path`);
process.exitCode = 1;
} else throw e;
} Prevention
- Point the `file` argument at a concrete file, never a directory or device path.
- Confirm the path kind with `stat -c %F <path>` before automating uploads.
- Loop over directory contents and upload each file individually if you have a folder of artifacts.
When it happens
Trigger: Passing a directory path as the `file` argument (e.g. `attachment-upload ./attachments '#general'`), pointing at `/dev/null`, a named pipe, or another special file. Also occurs when a variable that was supposed to hold a file path actually holds a directory.
Common situations: Users point at the folder containing the artifact instead of the artifact itself; scripts pass an output directory where a filename was expected; automation accidentally targets device files or sockets produced by another tool.
Related errors
- 封面文件不存在: ${path.resolve(coverPath)}
- file is empty: ${abs}
- ${label}文件不存在: ${resolved}
- Not a valid file: ${absPath}
- weixin create-draft cover-image is not a file: ${absPath}
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/473811cfaede3b39.
Report an issue: GitHub.