{"record":{"id":"2c453e43ca8b7204","repo":"jackwener/OpenCLI","slug":"slock-message-send-succeeded-without-returning-a-m","errorCode":null,"errorMessage":"Slock message-send succeeded without returning a message id; refusing to report a sent row.","messagePattern":"Slock message-send succeeded without returning a message id; refusing to report a sent row\\.","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/slock/message-send.js","lineNumber":57,"sourceCode":"    catch (e) { throw new ArgumentError(e.message); }\n\n    const extra = { asTask, attachmentIds };\n\n    if (kwargs['dry-run']) {\n      return [{\n        target, channelId: '(not resolved in dry-run)', content,\n        result: asTask ? 'dry-run (asTask)' : 'dry-run', messageId: null,\n      }];\n    }\n\n    await page.goto(SLOCK_HOME_URL);\n    const snippet = buildSendSnippet(target, content, cls, kwargs.server, extra);\n    const result = await page.evaluate(`(async () => { ${snippet} })()`);\n    const rows = dispatchEvaluateResult(result);\n    const r = rows[0] ?? {};\n    const messageId = r.id ?? r.messageId;\n    if (!messageId) {\n      throw new CommandExecutionError('Slock message-send succeeded without returning a message id; refusing to report a sent row.');\n    }\n    return [{\n      target,\n      channelId: r.channelId ?? '',\n      content,\n      result: 'sent',\n      messageId,\n    }];\n  },\n});\n\nfunction buildSendSnippet(target, content, cls, serverOverride, extra = {}) {\n  // R1 — raw override; authHeadersFragment owns the UUID-vs-slug resolution.\n  const override = serverOverride ?? null;\n  const contentJson = JSON.stringify(content);\n  const extraParts = [];\n  if (extra.asTask) extraParts.push('asTask: true');\n  if (Array.isArray(extra.attachmentIds) && extra.attachmentIds.length) {","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/slock/message-send.js#L39-L75","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["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","Update buildSendSnippet's parsing (`m.id ?? m.messageId`) to unwrap the new response shape, e.g. `m.message?.id`","Retry without --as-task to confirm plain message sends still return a top-level id","If targeting a self-hosted server, align it with the upstream Slock API response format for POST /messages"],"exampleFix":"// before (clis/slock/message-send.js, in-page snippet)\nconst m = await mres.json();\nconst messageId = m.id ?? m.messageId;\n// after\nconst m = await mres.json();\nconst inner = m.message ?? m.data ?? m;\nconst messageId = inner.id ?? inner.messageId ?? m.id;","handlingStrategy":"validation","validationCode":"const res = await page.evaluate(`(async () => { ${snippet} })()`);\nconst rows = dispatchEvaluateResult(res);\nconst r = rows[0] ?? {};\nconst messageId = r.id ?? r.messageId;\nif (!messageId) throw new Error(`POST /messages returned 200 without an id: ${JSON.stringify(r).slice(0, 200)}`);","typeGuard":"function hasMessageId(r) {\n  return r != null && typeof r === 'object' &&\n    typeof (r.id ?? r.messageId) === 'string' && (r.id ?? r.messageId).length > 0;\n}","tryCatchPattern":"try {\n  const row = await sendMessage(target, content);\n} catch (e) {\n  if (e.message.includes('without returning a message id')) {\n    console.error('Send may have succeeded but id was not returned; check server response shape before retrying to avoid duplicate messages.');\n  } else throw e;\n}","preventionTips":["Pin and monitor the Slock server version; diff POST /messages response schema after upgrades","Log the raw response body when `m.id ?? m.messageId` is missing","Avoid relying on --as-task outputs until the task response shape is confirmed","Do not blindly retry after this error — the message may already have been sent"],"tags":["browser-automation","api-response-shape","invariant-violation"],"backgroundTag":"missing-field-in-api-response","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}