{"record":{"id":"487f6b105ab21b28","repo":"jackwener/OpenCLI","slug":"twitter-post-completion-returned-a-malformed-statu-487f6b","errorCode":null,"errorMessage":"Twitter post completion returned a malformed status url.","messagePattern":"Twitter post completion returned a malformed status url\\.","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/twitter/post.js","lineNumber":67,"sourceCode":"    if (Object.prototype.hasOwnProperty.call(result, 'message') && result.message != null && typeof result.message !== 'string') {\n        throw new CommandExecutionError(`${context} returned a malformed message.`);\n    }\n    if (Object.prototype.hasOwnProperty.call(result, 'error') && result.error != null && typeof result.error !== 'string') {\n        throw new CommandExecutionError(`${context} returned a malformed error.`);\n    }\n    return result;\n}\n\nfunction validateSubmitStatusPair(result) {\n    if ((result.id && !result.url) || (!result.id && result.url)) {\n        throw new CommandExecutionError('Twitter post completion returned only one of id/url.');\n    }\n    if (!result.id && !result.url) return;\n    if (typeof result.id !== 'string' || !/^\\d+$/.test(result.id)) {\n        throw new CommandExecutionError('Twitter post completion returned a malformed status id.');\n    }\n    if (typeof result.url !== 'string') {\n        throw new CommandExecutionError('Twitter post completion returned a malformed status url.');\n    }\n    let url;\n    try {\n        url = new URL(result.url);\n    } catch {\n        throw new CommandExecutionError('Twitter post completion returned a malformed status url.');\n    }\n    const hostname = url.hostname.toLowerCase().replace(/^www\\./, '');\n    const match = url.pathname.match(/^\\/([^/]+)\\/status\\/(\\d+)\\/?$/);\n    if (!['x.com', 'twitter.com', 'mobile.twitter.com'].includes(hostname) || !match || match[2] !== result.id) {\n        throw new CommandExecutionError('Twitter post completion returned a malformed status url.');\n    }\n}\n\nasync function focusComposer(page) {\n    return requirePostActionResult(await page.evaluate(`(() => {\n        const visible = (el) => !!el && (el.offsetParent !== null || el.getClientRects().length > 0);\n        const boxes = Array.from(document.querySelectorAll('[data-testid=\"tweetTextarea_0\"]'));","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/twitter/post.js#L49-L85","documentation":"validateSubmitStatusPair parses the returned status url with new URL(). If the url is not a string, or fails to parse as an absolute URL (the try/catch around `new URL(result.url)`), this error is thrown. It ensures the CLI only reports shareable, well-formed tweet links.","triggerScenarios":"clickResult returns { ok: true, id, url } where url is a relative path like '/user/status/123', an empty string, or contains characters that make new URL() throw (unencoded spaces/bad escapes) — since the constructor runs in Node without a base, relative URLs fail.","commonSituations":"In-page script capturing location.pathname instead of location.href; X redirecting to a login or interstitial URL that still gets captured; idn/punycode or malformed redirect targets after posting.","solutions":["Make the in-page script return the absolute URL (location.href) rather than a path","Resolve a relative URL against the origin before returning: new URL(path, location.origin).href","Check whether the post redirected to a login/captcha page and re-authenticate the browser session"],"exampleFix":"// before (in-page script)\nreturn { ok: true, id, url: location.pathname };\n// after\nreturn { ok: true, id, url: location.origin + location.pathname };","handlingStrategy":"validation","validationCode":"let parsed; try { parsed = new URL(result.url); } catch { throw new Error('status url must be an absolute http(s) URL'); }\nif (!['https:', 'http:'].includes(parsed.protocol)) throw new Error('status url must be http(s)');","typeGuard":"function isAbsoluteHttpUrl(v) { if (typeof v !== 'string') return false; try { const u = new URL(v); return u.protocol === 'https:' || u.protocol === 'http:'; } catch { return false; } }","tryCatchPattern":"try {\n  const result = submitTweet(page, text);\n} catch (e) {\n  if (String(e.message).includes('malformed status url')) { /* url was relative or unparseable; capture location.href instead */ }\n  throw e;\n}","preventionTips":["Return absolute URLs (location.href), never pathname alone","Resolve relative paths with new URL(path, location.origin).href","Re-authenticate if the captured URL is a login interstitial"],"tags":["browser-automation","url-validation","x-twitter"],"backgroundTag":"malformed-status-url","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}