jackwener/OpenCLI · error · ArgumentError

threadChannelId must be a full UUID (got "${id}"). Get it fr

Error message

threadChannelId must be a full UUID (got "${id}"). Get it from thread-list or message-read's threadChannelId column.

What it means

thread-state (built via makeThreadStateCommand for read/mark verbs) requires threadChannelId to be a full UUID and validates it with UUID_RE before issuing any request; a non-UUID input throws this ArgumentError immediately with a pointer to where a valid id can be obtained (thread-list or message-read).

Source

Thrown at clis/slock/thread-state.js:30

export function makeThreadStateCommand({ name, verb, resultLabel, description }) {
  cli({
    site: SLOCK_SITE,
    name,
    access: 'write',
    description,
    domain: SLOCK_DOMAIN,
    strategy: Strategy.COOKIE,
    browser: true,
    siteSession: 'persistent',
    args: [
      { name: 'threadChannelId', positional: true, required: true, help: 'Thread channel UUID (from thread-list / message-read)' },
      { name: 'server', help: 'Override active server' },
    ],
    columns: ['threadChannelId', 'result'],
    func: async (page, kwargs) => {
      const id = String(kwargs.threadChannelId ?? '').trim();
      if (!UUID_RE.test(id)) {
        throw new ArgumentError(`threadChannelId must be a full UUID (got "${id}"). Get it from thread-list or message-read's threadChannelId column.`);
      }
      await page.goto(SLOCK_HOME_URL);
      const snippet = buildFetchSnippet({
        method: 'POST',
        path: `/channels/threads/${verb}`,
        body: { threadChannelId: id },
        serverScoped: true,
        serverIdOverride: kwargs.server,
      });
      const result = await page.evaluate(`(async () => { ${snippet} })()`);
      dispatchEvaluateResult(result);
      return [{ threadChannelId: id, result: resultLabel }];
    },
  });
}

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Get the exact threadChannelId from the `thread-list` command's threadChannelId column.
  2. Or take it from message-read's threadChannelId column as the error suggests.
  3. Trim the input; ensure no stray quotes or partial (non-UUID) ids are passed.
  4. Validate with a UUID regex in your wrapper before invoking.

Example fix

// before
await cli.run(['thread-state', '--thread-channel-id', msg.parentMessageId]);
// after
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(msg.threadChannelId))
  throw new Error('need a full UUID threadChannelId');
await cli.run(['thread-state', '--thread-channel-id', msg.threadChannelId]);
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;
const id = String(threadChannelId ?? '').trim();
if (!UUID_RE.test(id)) throw new Error(`threadChannelId must be a full UUID, got: ${id}`);

Type guard

function isUuid(s) { return typeof s === 'string' && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(s.trim()); }

Try / catch

try {
  await cli.run(['thread-state', '--thread-channel-id', id]);
} catch (e) {
  if (String(e.message).includes('must be a full UUID')) {
    const threads = await cli.run(['thread-list']);
    console.error('valid ids:', threads.map(t => t.threadChannelId));
  } else throw e;
}

Prevention

When it happens

Trigger: Passing a short/partial id, a human-readable thread name, a parent message id, or a value with surrounding whitespace/quotes to `thread-state --thread-channel-id`.

Common situations: Copy/paste truncated ids from logs; confusing threadChannelId with parentMessageId; interpolating ids into shell commands where quotes become part of the value.

Related errors


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