{"record":{"id":"45a0b97eed00bc2c","repo":"jackwener/OpenCLI","slug":"invalid-tweet-url-value","errorCode":null,"errorMessage":"Invalid tweet URL: ${value}","messagePattern":"Invalid tweet URL: (.+?)","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/twitter/shared.js","lineNumber":57,"sourceCode":"]);\n\nfunction isTwitterHost(hostname) {\n    return TWEET_HOSTS.has(hostname)\n        || hostname.endsWith('.x.com')\n        || hostname.endsWith('.twitter.com');\n}\n\nexport function parseTweetUrl(rawUrl) {\n    const value = String(rawUrl ?? '').trim();\n    if (!value) {\n        throw new ArgumentError('twitter tweet URL cannot be empty', 'Example: opencli twitter retweet https://x.com/jack/status/20');\n    }\n    let parsed;\n    try {\n        parsed = new URL(value);\n    }\n    catch {\n        throw new ArgumentError(`Invalid tweet URL: ${value}`, 'Use a full https://x.com/<user>/status/<id> URL');\n    }\n    const hostname = parsed.hostname.toLowerCase();\n    if (parsed.protocol !== 'https:' || !isTwitterHost(hostname)) {\n        throw new ArgumentError(`Invalid tweet URL host: ${value}`, 'Use a full https://x.com/<user>/status/<id> URL');\n    }\n    const match = parsed.pathname.match(TWEET_PATH_PATTERN);\n    if (!match?.[1]) {\n        throw new ArgumentError(`Could not extract tweet ID from URL: ${value}`, 'Use a full https://x.com/<user>/status/<id> URL');\n    }\n    return {\n        id: match[1],\n        url: parsed.toString(),\n    };\n}\n\n/**\n * Build a JS source fragment that, when embedded inside a `page.evaluate(...)`\n * IIFE, declares browser-side helpers for scoping operations to a specific","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/twitter/shared.js#L39-L75","documentation":"ArgumentError from parseTweetUrl when the string is non-empty but cannot be parsed as a URL (new URL(value) throws). The library rejects it early with guidance to use a full https://x.com/<user>/status/<id> URL.","triggerScenarios":"Passing 'jack/status/20', 'x.com/jack/status/20' (no scheme is actually parseable by URL only with base — bare host without scheme throws), 'opencli twitter retweet 20' (just the ID), or a URL with stray spaces.","commonSituations":"Users pasting just the tweet ID or path from the address bar minus scheme, shell mangling, or code passing a tweet object instead of its URL string.","solutions":["Use the full URL including https:// scheme","If you only have the ID, build the URL: `https://x.com/<user>/status/<id>` or `https://x.com/i/status/<id>` if supported","Trim whitespace/newlines from the input before passing"],"exampleFix":"// before\nopencli twitter retweet 20\n// after\nopencli twitter retweet https://x.com/jack/status/20","handlingStrategy":"validation","validationCode":"function normalizeTweetUrl(v) {\n  const s = String(v ?? '').trim();\n  if (/^\\d+$/.test(s)) return `https://x.com/i/status/${s}`; // bare ID\n  if (!/^https:\\/\\//.test(s)) return `https://${s}`;\n  return s;\n}\n// then verify: new URL(normalizeTweetUrl(v)) does not throw","typeGuard":"function isParseableUrl(v) { try { new URL(String(v).trim()); return true; } catch { return false; } }","tryCatchPattern":"try {\n  await opencli.twitter.retweet(url);\n} catch (e) {\n  if (e.name === 'ArgumentError' && /Invalid tweet URL:/.test(e.message)) {\n    console.error('Use https://x.com/<user>/status/<id>');\n  } else throw e;\n}","preventionTips":["Always include the https:// scheme","Expand bare tweet IDs to full URLs in your wrapper","Trim whitespace/newlines from pasted input","Validate with new URL() before calling the CLI"],"tags":["validation","argument","twitter","url"],"backgroundTag":"invalid-url-format","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}