{"record":{"id":"94236ffbd61cecf9","repo":"jackwener/OpenCLI","slug":"thread-or-recipient-is-required","errorCode":null,"errorMessage":"thread or recipient is required","messagePattern":"thread or recipient is required","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/linkedin/salesnav-thread.js","lineNumber":138,"sourceCode":"function threadMatchesInput(row, parsed) {\n  if (!row || !parsed) return false;\n  const [kind, criterion] = parsed;\n  if (kind === 'thread_id') return row.thread_id === criterion;\n  if (kind === 'recipient_urn') {\n    return (row.participants || []).some((p) => normalizeWhitespace(p.entity_urn) === criterion);\n  }\n  if (kind === 'name') {\n    const needle = normalizeWhitespace(criterion).toLowerCase();\n    if (!needle) return false;\n    if (normalizeWhitespace(row.person_name).toLowerCase() === needle) return true;\n    return (row.participants || []).some((p) => normalizeWhitespace(p.name).toLowerCase() === needle);\n  }\n  return false;\n}\n\nasync function resolveThreadId(page, input, { maxPages = 30 } = {}) {\n  const parsed = parseThreadInput(input);\n  if (parsed[0] === 'empty') throw new ArgumentError('thread or recipient is required');\n  if (parsed[0] === 'thread_id') return parsed[1];\n  const inboxRows = await fetchInboxRows(page, { limit: 500, maxPages });\n  const match = inboxRows.find((row) => threadMatchesInput(row, parsed));\n  if (!match) {\n    throw new EmptyResultError('linkedin salesnav-thread', `No Sales Navigator thread matched ${input}`);\n  }\n  return match.thread_id;\n}\n\nasync function fetchThreadWithPagination(page, csrf, threadId, limit = DEFAULT_MESSAGE_LIMIT) {\n  let requested = THREAD_PAGE_SIZE;\n  if (limit < requested) requested = limit;\n  let thread = null;\n  for (let attempts = 0; attempts < 30; attempts += 1) {\n    thread = await fetchSalesnavJson(page, csrf, threadApiUrl(threadId, requested), 'Sales Navigator messaging thread API');\n    const total = Number(thread?.totalMessageCount || 0);\n    const have = Array.isArray(thread?.messages) ? thread.messages.length : 0;\n    if (have >= limit || (total && have >= total) || requested >= limit) break;","sourceCodeStart":120,"sourceCodeEnd":156,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/linkedin/salesnav-thread.js#L120-L156","documentation":"resolveThreadId is the Sales Navigator thread resolver; it first parses the user-supplied input into a tagged variant. If parseThreadInput classifies the input as 'empty' (missing/whitespace-only thread id, lead URL, recipient URN, or participant name), it throws ArgumentError('thread or recipient is required'). The library throws this to fail fast before doing an expensive inbox scrape it could never match against.","triggerScenarios":"Calling the 'linkedin salesnav-thread' CLI (via threadId) with an undefined, null, or whitespace-only positional 'thread-or-recipient' argument so parseThreadInput returns ['empty'].","commonSituations":"Scripting the CLI from a shell variable that is unset or empty (e.g. $THREAD was never exported); passing an argument that normalizes to whitespace; wiring where the positional argument was given to the wrong flag name.","solutions":["Pass a non-empty positional value: a Sales Navigator inbox URL, thread id, Sales Navigator lead URL, recipient URN, or exact participant name.","Check that the shell variable/CI secret feeding the argument is actually set before invoking the CLI.","Trim the input; a string of only spaces is treated as empty.","If you only have a name, use the exact participant name as displayed in Sales Navigator, or resolve to a recipient URN first."],"exampleFix":"// before\nnode cli.js linkedin salesnav-thread \"$THREAD\"   # THREAD unset -> empty\n// after\n: \"${THREAD:?set THREAD to a Sales Navigator thread id, lead URL, or recipient urn}\"\nnode cli.js linkedin salesnav-thread \"$THREAD\"","handlingStrategy":"validation","validationCode":"if (!input || !String(input).trim()) throw new Error('thread-or-recipient must be a non-empty thread id, lead URL, recipient urn, or participant name');","typeGuard":"function hasThreadInput(v){ return typeof v === 'string' && v.trim().length > 0; }","tryCatchPattern":"try { await threadId(input) } catch (e) { if (e instanceof ArgumentError && /thread or recipient is required/.test(e.message)) { /* surface config problem */ } else throw e; }","preventionTips":["Check the feeding shell/config variable for emptiness before invoking (use ${VAR:?} in bash).","Trim inputs before passing; whitespace-only is treated as empty.","Prefer passing an explicit thread id or URN over free-text names.","Add a CI assertion that required CLI arguments are non-empty."],"tags":["argument-error","input-validation","linkedin","sales-navigator"],"backgroundTag":"missing-required-argument","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}