jackwener/OpenCLI · error · ArgumentError

attachmentId "${id}" is not a UUID

Error message

attachmentId "${id}" is not a UUID

What it means

This ArgumentError is thrown by the attachment-url command when the attachmentId argument does not match the UUID regular expression. Attachment IDs are UUIDs issued by the server; anything else would produce a malformed API path, so the CLI validates the shape before navigating and running the fetch snippet.

Source

Thrown at clis/slock/attachment-url.js:33

import { UUID_RE } from './resolve.js';

cli({
  site: SLOCK_SITE,
  name: 'attachment-url',
  access: 'read',
  description: 'Get a short-lived signed CDN URL for an attachment (does not download bytes).',
  domain: SLOCK_DOMAIN,
  strategy: Strategy.COOKIE,
  browser: true,
  siteSession: 'persistent',
  args: [
    { name: 'attachmentId', positional: true, required: true, help: 'Attachment UUID' },
    { name: 'server', help: 'Override active server slug' },
  ],
  columns: ['attachmentId', 'url', 'expiresAt'],
  func: async (page, kwargs) => {
    const id = String(kwargs.attachmentId ?? '').trim();
    if (!UUID_RE.test(id)) throw new ArgumentError(`attachmentId "${id}" is not a UUID`);
    await page.goto(SLOCK_HOME_URL);
    const snippet = buildFetchSnippet({
      method: 'GET',
      path: `/attachments/${encodeURIComponent(id)}/url`,
      serverScoped: true,
      serverIdOverride: kwargs.server,
    });
    const result = await page.evaluate(`(async () => { ${snippet} })()`);
    const rows = dispatchEvaluateResult(result);
    const data = Array.isArray(rows) ? rows[0] : rows;
    if (!data?.url) {
      throw new CommandExecutionError(`no signed url returned for attachment ${id}`);
    }
    return [{
      attachmentId: id,
      url: data?.url ?? null,
      expiresAt: data?.expiresAt ?? null,
    }];

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Run the attachment listing command to get the correct UUID-form attachmentId and pass that.
  2. Strip any URL/path prefix so only the bare UUID remains (e.g. extract the last path segment).
  3. Validate the format first: /^[0-9a-f]{8}-[0-9a-f]{4}-.../i (standard 8-4-4-4-12 hex pattern) before invoking.
  4. Confirm the attachment exists on the target server; ids from other servers won't resolve.

Example fix

// before
await attachmentUrl(page, process.argv[2]); // "https://slock.example/attachments/abc"
// after
const id = process.argv[2].split('/').pop();
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id)) throw new Error('not a UUID');
await attachmentUrl(page, id);
Defensive patterns

Strategy: validation

Validate before calling

const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
if (!UUID_RE.test(attachmentId)) throw new Error(`attachmentId must be a UUID, got: ${attachmentId}`);

Type guard

const isUuid = (v) => typeof v === 'string' && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(v);

Try / catch

try {
  await attachmentUrl(page, id);
} catch (e) {
  if (e instanceof ArgumentError && e.message.includes('is not a UUID')) {
    console.error(`Bad id "${id}" — re-list attachments to get the UUID`);
  } else throw e;
}

Prevention

When it happens

Trigger: Passing a non-UUID string as the positional attachmentId — e.g. a numeric database id, a URL, a filename, an empty string after trimming, or an ID from a different system.

Common situations: Copying the wrong column from a previous listing (e.g. server id instead of attachment id), pasting a URL fragment instead of the bare UUID, or using an id from another chat platform.

Related errors


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