{"record":{"id":"40f2c9b345822a97","repo":"jackwener/OpenCLI","slug":"dm-target-must-be-dm-userid-or-dm-name","errorCode":null,"errorMessage":"dm target must be \"dm:<userId>\" or \"dm:@name\".","messagePattern":"dm target must be \"dm:<userId>\" or \"dm:@name\"\\.","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/slock/resolve.js","lineNumber":24,"sourceCode":"export function classifyThreadTarget(raw) {\n  const s = String(raw ?? '').trim();\n  const m = s.match(/^(#?[^:]+):([A-Za-z0-9-]{6,})$/);\n  if (!m) return null;\n  return { parentTarget: m[1], parentMsgId: m[2] };\n}\n\n// Target kinds:\n//   { kind: 'channel-uuid', channelId }\n//   { kind: 'channel-name', name }\n//   { kind: 'dm-uuid', userId }\n//   { kind: 'dm-name', name }\n//   { kind: 'thread', parentTarget, parentMsgId }\nexport function classifyTarget(raw) {\n  const v = String(raw ?? '').trim();\n  if (!v) throw new ArgumentError('target required: \"#channel\", \"#channel:threadShortId\", \"dm:@name\", \"dm:<userId>\", or channelId UUID.');\n  if (v.startsWith('dm:')) {\n    const rest = v.slice(3);\n    if (!rest) throw new ArgumentError('dm target must be \"dm:<userId>\" or \"dm:@name\".');\n    if (UUID_RE.test(rest)) return { kind: 'dm-uuid', userId: rest };\n    if (rest.startsWith('@')) return { kind: 'dm-name', name: rest.slice(1) };\n    throw new ArgumentError('dm target must be \"dm:<userId-uuid>\" or \"dm:@name\".');\n  }\n  const tt = classifyThreadTarget(v);\n  if (tt) return { kind: 'thread', ...tt };\n  if (UUID_RE.test(v)) return { kind: 'channel-uuid', channelId: v };\n  return { kind: 'channel-name', name: v.replace(/^#/, '').toLowerCase() };\n}\n\nconst SHORT_ID_HINT =\n  'short ids (the 8-hex `msg=...` form in channel headers) are NOT accepted — use the FULL UUID ' +\n  'from `bookmark-list` / `message-read` output.';\n\nexport function assertMessageIdShape(messageId) {\n  const v = String(messageId ?? '').trim();\n  if (!v) throw new ArgumentError('messageId required');\n  if (!UUID_RE.test(v)) {","sourceCodeStart":6,"sourceCodeEnd":42,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/slock/resolve.js#L6-L42","documentation":"When the target starts with `dm:` but nothing follows the prefix, classifyTarget throws this ArgumentError. A DM target must carry either a user UUID or an @name after the colon.","triggerScenarios":"Passing exactly `\"dm:\"` (or `\"dm: \"` which trims... no — trailing whitespace after the colon is preserved in `rest`) — precisely, `raw` is `dm:` with an empty remainder, e.g. `slock message-send dm: \"hello\"` or a variable `${user}` interpolating to empty: `dm:${user}`.","commonSituations":"Templating bug where the user id/name variable is empty (`dm:${USER_ID}` with USER_ID unset); copy error leaving the prefix without the recipient; splitting a target string on the wrong delimiter.","solutions":["Append the recipient: `dm:@username` or `dm:<user-uuid>`","Fix the variable interpolation that dropped the user value","Verify the target string before calling, e.g. `target.includes(':') && target.split(':')[1]`"],"exampleFix":"// before\nconst target = `dm:${userId}`; // userId = ''\n// after\nif (!userId) throw new Error('userId required for dm target');\nconst target = `dm:${userId}`;","handlingStrategy":"validation","validationCode":"if (/^dm:$/.test(String(target ?? '').trim())) throw new Error('dm target missing recipient; use \"dm:<userId-uuid>\" or \"dm:@name\"');","typeGuard":"function isDmTarget(v) {\n  if (typeof v !== 'string') return false;\n  const rest = v.trim().slice(3);\n  return v.trim().startsWith('dm:') && rest.length > 0 &&\n    (/^@/.test(rest) || /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(rest));\n}","tryCatchPattern":"try {\n  await cli('message-send', { target, content });\n} catch (e) {\n  if (e instanceof ArgumentError && e.message.includes('dm target must be')) {\n    console.error(`dm target \"${target}\" had no recipient after \"dm:\"; append @name or a user UUID.`);\n  } else throw e;\n}","preventionTips":["Guard interpolated dm targets: throw if the user variable is empty before building `dm:${user}`","Prefer dm-uuid form in automation for unambiguous resolution","Sanity-check the assembled target string with a regex before invoking","Avoid string-splitting pipelines that can drop the text after the colon"],"tags":["argument-validation","target-parsing","dm"],"backgroundTag":"invalid-argument-format","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}