{"record":{"id":"5d5934c2a6f1d7dd","repo":"jackwener/OpenCLI","slug":"twitter-post-completion-returned-only-one-of-id-ur","errorCode":null,"errorMessage":"Twitter post completion returned only one of id/url.","messagePattern":"Twitter post completion returned only one of id/url\\.","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/twitter/post.js","lineNumber":60,"sourceCode":"}\n\nfunction requirePostActionResult(value, context) {\n    const result = unwrapBrowserResult(value);\n    if (!result || typeof result !== 'object' || Array.isArray(result) || typeof result.ok !== 'boolean') {\n        throw new CommandExecutionError(`${context} returned a malformed result.`);\n    }\n    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.');","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/twitter/post.js#L42-L78","documentation":"After submitTweet clicks the post button, validateSubmitStatusPair checks the completion result. A successful tweet must supply the new status id and its url together; supplying exactly one of them means the browser script returned a partially-formed completion, so this error is thrown. It protects callers from using a tweet id without a shareable url (or vice versa).","triggerScenarios":"submitTweet's clickResult evaluate returns { ok: true, id: '123' } without url, or { ok: true, url: '...' } without id — e.g. the script scraped the URL from the address bar but failed to extract the status id from the DOM, or a redirect completed but the id regex failed.","commonSituations":"X UI changes moving the data-testid or anchor that carried the status id; the compose page redirecting to a URL the script parses but whose id extraction step throws silently; partial page loads right after clicking Post where only the URL is available yet.","solutions":["Retry the post — a transient partial result often resolves once the composer page fully redirects","Update post.js's clickResult in-page script to derive both id and url from the same source (parse the id out of the returned URL)","Log the full result object in clickResult to see which of id/url was captured","Pin/verify the CLI version matches the current X compose flow"],"exampleFix":"// before (in-page script): returns id scraped separately\nreturn { ok: true, id: statusId };\n// after: derive both from the redirected URL\nconst url = location.href;\nconst m = url.match(/\\/status\\/(\\d+)/);\nif (!m) return { ok: false, error: 'no status url after posting' };\nreturn { ok: true, id: m[1], url };","handlingStrategy":"validation","validationCode":"function submitPairComplete(r) { const hasId = !!(r && r.id), hasUrl = !!(r && r.url); return hasId === hasUrl; }\nif (!submitPairComplete(clickResult)) throw new Error('posting returned incomplete result; retry');","typeGuard":"function isCompleteSubmitResult(v) { return typeof v === 'object' && v !== null && ('id' in v) === ('url' in v); }","tryCatchPattern":"try {\n  const result = submitTweet(page, text);\n} catch (e) {\n  if (String(e.message).includes('only one of id/url')) { /* retry once; likely partial page load */ }\n  throw e;\n}","preventionTips":["Derive both id and url from the same redirected URL in in-page scripts","Wait for the /status/<id> redirect before capturing the result","Retry once on partial results before surfacing the failure"],"tags":["browser-automation","data-integrity","x-twitter"],"backgroundTag":"incomplete-post-result","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}