jackwener/OpenCLI · error · CommandExecutionError

Slock message-send succeeded without returning a message id;

Error message

Slock message-send succeeded without returning a message id; refusing to report a sent row.

What it means

message-send drives the Slock web app in a browser page and POSTs to /messages via an in-page fetch snippet. The server returned 200 and the snippet reported kind:'ok', but the JSON body contained neither an `id` nor a `messageId` field. The CLI deliberately refuses to fabricate a 'sent' output row without a real message id, because downstream commands (reaction-add, message-read, bookmark) require it.

Source

Thrown at clis/slock/message-send.js:57

    catch (e) { throw new ArgumentError(e.message); }

    const extra = { asTask, attachmentIds };

    if (kwargs['dry-run']) {
      return [{
        target, channelId: '(not resolved in dry-run)', content,
        result: asTask ? 'dry-run (asTask)' : 'dry-run', messageId: null,
      }];
    }

    await page.goto(SLOCK_HOME_URL);
    const snippet = buildSendSnippet(target, content, cls, kwargs.server, extra);
    const result = await page.evaluate(`(async () => { ${snippet} })()`);
    const rows = dispatchEvaluateResult(result);
    const r = rows[0] ?? {};
    const messageId = r.id ?? r.messageId;
    if (!messageId) {
      throw new CommandExecutionError('Slock message-send succeeded without returning a message id; refusing to report a sent row.');
    }
    return [{
      target,
      channelId: r.channelId ?? '',
      content,
      result: 'sent',
      messageId,
    }];
  },
});

function buildSendSnippet(target, content, cls, serverOverride, extra = {}) {
  // R1 — raw override; authHeadersFragment owns the UUID-vs-slug resolution.
  const override = serverOverride ?? null;
  const contentJson = JSON.stringify(content);
  const extraParts = [];
  if (extra.asTask) extraParts.push('asTask: true');
  if (Array.isArray(extra.attachmentIds) && extra.attachmentIds.length) {

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Inspect the actual /messages response body (browser devtools Network tab, or log `m` in the snippet at clis/slock/message-send.js:84-86) to see where the id now lives
  2. Update buildSendSnippet's parsing (`m.id ?? m.messageId`) to unwrap the new response shape, e.g. `m.message?.id`
  3. Retry without --as-task to confirm plain message sends still return a top-level id
  4. If targeting a self-hosted server, align it with the upstream Slock API response format for POST /messages

Example fix

// before (clis/slock/message-send.js, in-page snippet)
const m = await mres.json();
const messageId = m.id ?? m.messageId;
// after
const m = await mres.json();
const inner = m.message ?? m.data ?? m;
const messageId = inner.id ?? inner.messageId ?? m.id;
Defensive patterns

Strategy: validation

Validate before calling

const res = await page.evaluate(`(async () => { ${snippet} })()`);
const rows = dispatchEvaluateResult(res);
const r = rows[0] ?? {};
const messageId = r.id ?? r.messageId;
if (!messageId) throw new Error(`POST /messages returned 200 without an id: ${JSON.stringify(r).slice(0, 200)}`);

Type guard

function hasMessageId(r) {
  return r != null && typeof r === 'object' &&
    typeof (r.id ?? r.messageId) === 'string' && (r.id ?? r.messageId).length > 0;
}

Try / catch

try {
  const row = await sendMessage(target, content);
} catch (e) {
  if (e.message.includes('without returning a message id')) {
    console.error('Send may have succeeded but id was not returned; check server response shape before retrying to avoid duplicate messages.');
  } else throw e;
}

Prevention

When it happens

Trigger: The in-page POST to `${SLOCK_API_BASE}/messages` returns HTTP 200 with a JSON body lacking `id`/`messageId` — e.g. the API shape changed and now wraps the message differently (e.g. `{message: {...}}` or `{data: {...}}`), or the endpoint returns an empty object/ack for some content types (e.g. asTask submissions that only return a task reference).

Common situations: Slock server/API version updated and the /messages response schema no longer matches what the snippet parses; sending with --as-task where the backend returns a task object without an `id` field at the top level; a proxy or modified frontend returning 200 with an unexpected body; running against a self-hosted/alternate Slock server whose response format differs.

Related errors


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