{"record":{"id":"d6b5a1a4543a2105","repo":"jackwener/OpenCLI","slug":"slock-thread-follow-succeeded-without-returning-a","errorCode":null,"errorMessage":"Slock thread-follow succeeded without returning a thread channel id for parent message ${id}.","messagePattern":"Slock thread-follow succeeded without returning a thread channel id for parent message (.+?)\\.","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/slock/thread-follow.js","lineNumber":42,"sourceCode":"  ],\n  columns: ['parentMessageId', 'threadChannelId', 'result'],\n  func: async (page, kwargs) => {\n    let id;\n    try { id = assertMessageIdShape(String(kwargs.parentMessageId ?? '')); }\n    catch (e) { throw new ArgumentError(e.message); }\n    await page.goto(SLOCK_HOME_URL);\n    const snippet = buildFetchSnippet({\n      method: 'POST',\n      path: '/channels/threads/follow',\n      body: { parentMessageId: id },\n      serverScoped: true,\n      serverIdOverride: kwargs.server,\n    });\n    const result = await page.evaluate(`(async () => { ${snippet} })()`);\n    const data = dispatchEvaluateResult(result);\n    const threadChannelId = data?.threadChannelId ?? data?.channelId ?? data?.id;\n    if (!threadChannelId) {\n      throw new CommandExecutionError(`Slock thread-follow succeeded without returning a thread channel id for parent message ${id}.`);\n    }\n    return [{ parentMessageId: id, threadChannelId, result: 'followed' }];\n  },\n});\n","sourceCodeStart":24,"sourceCodeEnd":47,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/slock/thread-follow.js#L24-L47","documentation":"After following a thread, thread-follow reads `threadChannelId ?? channelId ?? id` from the API response; if none is present it throws this CommandExecutionError because the whole point of the command is to report the created/found thread channel id. Success without an identifiable channel id is treated as contract drift.","triggerScenarios":"POST /channels/threads/follow returns 2xx but the body lacks threadChannelId/channelId/id — e.g. the endpoint returns {kind:'ok'} with no payload, an already-following response with an empty body, or a renamed field in a newer API version.","commonSituations":"Following a thread you already follow (API short-circuits with no payload); server version older/newer than the CLI expects; proxies stripping response bodies.","solutions":["Log the raw evaluate result to see what the follow endpoint actually returned.","Handle the already-following case separately — look the channel id up via thread-list instead.","Update the CLI/server to matching versions so the response includes threadChannelId.","If the field is nested, unwrap it in dispatchEvaluateResult before the check."],"exampleFix":"// before\nconst data = dispatchEvaluateResult(result);\nconst threadChannelId = data?.threadChannelId ?? data?.channelId ?? data?.id;\n// after\nconst raw = dispatchEvaluateResult(result);\nconst data = raw?.thread ?? raw;\nconst threadChannelId = data?.threadChannelId ?? data?.channelId ?? data?.id;","handlingStrategy":"fallback","validationCode":null,"typeGuard":"function hasThreadChannelId(d) { return !!(d?.threadChannelId ?? d?.channelId ?? d?.id); }","tryCatchPattern":"try {\n  const r = await cli.run(['thread-follow', '--parent-message-id', id]);\n} catch (e) {\n  if (String(e.message).includes('without returning a thread channel id')) {\n    // likely already-following: look it up\n    const threads = await cli.run(['thread-list']);\n    const hit = threads.find(t => t.parentMessageId === id);\n  } else throw e;\n}","preventionTips":["Check thread-list first — the thread may already exist","Log raw follow responses when upgrading the server","Unwrap nested payloads before validating"],"tags":["contract-drift","missing-field","threads"],"backgroundTag":"api-response-contract-drift","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}