{"record":{"id":"8e2d0949cc50f200","repo":"jackwener/OpenCLI","slug":"file-not-readable-abs-e-message","errorCode":null,"errorMessage":"file not readable: ${abs} (${e.message})","messagePattern":"file not readable: (.+?) \\((.+?)\\)","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/slock/attachment-upload.js","lineNumber":56,"sourceCode":"  domain: SLOCK_DOMAIN,\n  strategy: Strategy.COOKIE,\n  browser: true,\n  siteSession: 'persistent',\n  args: [\n    { name: 'file', positional: true, required: true, help: 'Local file path to upload (single file; max 50 MB)' },\n    { name: 'channel', positional: true, required: true, help: 'channelId UUID or #name — server requires the attachment be scoped to a channel' },\n    { name: 'server', help: 'Override active server slug' },\n  ],\n  columns: ['attachmentId', 'filename', 'mimeType', 'sizeBytes'],\n  func: async (page, kwargs) => {\n    const filePath = String(kwargs.file ?? '').trim();\n    if (!filePath) throw new ArgumentError('file path required');\n    const channel = String(kwargs.channel ?? '').trim();\n    if (!channel) throw new ArgumentError('channel required (UUID or #name); server rejects uploads without channelId');\n    const abs = path.resolve(filePath);\n    let stat;\n    try { stat = fs.statSync(abs); }\n    catch (e) { throw new ArgumentError(`file not readable: ${abs} (${e.message})`); }\n    if (!stat.isFile()) throw new ArgumentError(`not a regular file: ${abs}`);\n    if (stat.size === 0) throw new ArgumentError(`file is empty: ${abs}`);\n    if (stat.size > MAX_BYTES) {\n      throw new ArgumentError(`file is ${stat.size} bytes, exceeds server limit ${MAX_BYTES} (50 MiB). Split or compress before upload.`);\n    }\n\n    const buf = fs.readFileSync(abs);\n    const filename = path.basename(abs);\n    const b64 = buf.toString('base64');\n\n    await page.goto(SLOCK_HOME_URL);\n\n    const snippet = `\n      ${authHeadersFragment({ serverScoped: true, serverIdOverride: kwargs.server })}\n      ${channelResolveFragment(channel)}\n      // multipart wants the browser to set its own boundary — strip content-type.\n      const uploadHeaders = { authorization: headers.authorization, accept: headers.accept };\n      if (headers['x-server-id']) uploadHeaders['x-server-id'] = headers['x-server-id'];","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/slock/attachment-upload.js#L38-L74","documentation":"This ArgumentError is thrown by `attachment-upload` in clis/slock/attachment-upload.js:56 when `fs.statSync(abs)` fails on the resolved path. It wraps the underlying Node error message (ENOENT, EACCES, ENOTDIR, etc.), meaning the path does not exist, is inaccessible to the current user, or a path component is not a directory. The command checks locally before spinning up the browser/upload flow.","triggerScenarios":"Uploading a file path that does not exist (typo, wrong working directory), a path the process cannot read (permission denied), a broken symlink target, or a path with unexpanded `~` or glob characters. Also fires when a preceding pipeline step that should have produced the file failed silently.","commonSituations":"Relative paths resolved against an unexpected CWD in cron/CI; missing `~` expansion when the path is single-quoted in shell (`'~/file.pdf'`); files deleted between generation and upload; running the CLI as a user without read permission on the file or a parent directory.","solutions":["Verify the file exists and the path is spelled correctly: `ls -l <path>`; use an absolute path to rule out CWD confusion.","Expand `~` yourself (unquoted tilde or `$HOME`) — the CLI's `path.resolve` does not expand tilde.","Check read permissions on the file and every parent directory (`namei -l <path>`); fix with chmod/chown or run as a user with access.","Confirm the producing step of the file completed successfully before uploading."],"exampleFix":"// before: tilde never expands inside quotes -> ENOENT\nattachment-upload '~/Downloads/report.pdf' '#general'\n// after: let the shell expand tilde or use $HOME\nattachment-upload ~/Downloads/report.pdf '#general'","handlingStrategy":"validation","validationCode":"import fs from 'node:fs';\nimport path from 'node:path';\nconst abs = path.resolve(filePath);\nif (!fs.existsSync(abs)) {\n  throw new Error(`file not readable: ${abs} does not exist`);\n}\nfs.accessSync(abs, fs.constants.R_OK); // throws EACCES with a clear message","typeGuard":null,"tryCatchPattern":"try {\n  await upload({ file: abs, channel });\n} catch (e) {\n  if (e instanceof ArgumentError && e.message.startsWith('file not readable')) {\n    console.error(`Check the path exists and is readable: ${e.message}`);\n    process.exitCode = 1;\n  } else throw e;\n}","preventionTips":["Use absolute paths in scripts/cron/CI so results never depend on the working directory.","Avoid single-quoted `~/...` paths — the CLI does not expand tilde; let the shell or `$HOME` do it.","Run `fs.accessSync(p, fs.constants.R_OK)` (or `test -r`) as a preflight check before uploading.","Verify the file-producing step succeeded before the upload step in pipelines."],"tags":["argument-error","file-not-found","filesystem","permissions"],"backgroundTag":"file-not-found","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}