jackwener/OpenCLI · error · CommandExecutionError
Twitter post completion returned a malformed status url.
Error message
Twitter post completion returned a malformed status url.
What it means
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.
Source
Thrown at clis/twitter/post.js:67
if (Object.prototype.hasOwnProperty.call(result, 'message') && result.message != null && typeof result.message !== 'string') {
throw new CommandExecutionError(`${context} returned a malformed message.`);
}
if (Object.prototype.hasOwnProperty.call(result, 'error') && result.error != null && typeof result.error !== 'string') {
throw new CommandExecutionError(`${context} returned a malformed error.`);
}
return result;
}
function validateSubmitStatusPair(result) {
if ((result.id && !result.url) || (!result.id && result.url)) {
throw new CommandExecutionError('Twitter post completion returned only one of id/url.');
}
if (!result.id && !result.url) return;
if (typeof result.id !== 'string' || !/^\d+$/.test(result.id)) {
throw new CommandExecutionError('Twitter post completion returned a malformed status id.');
}
if (typeof result.url !== 'string') {
throw new CommandExecutionError('Twitter post completion returned a malformed status url.');
}
let url;
try {
url = new URL(result.url);
} catch {
throw new CommandExecutionError('Twitter post completion returned a malformed status url.');
}
const hostname = url.hostname.toLowerCase().replace(/^www\./, '');
const match = url.pathname.match(/^\/([^/]+)\/status\/(\d+)\/?$/);
if (!['x.com', 'twitter.com', 'mobile.twitter.com'].includes(hostname) || !match || match[2] !== result.id) {
throw new CommandExecutionError('Twitter post completion returned a malformed status url.');
}
}
async function focusComposer(page) {
return requirePostActionResult(await page.evaluate(`(() => {
const visible = (el) => !!el && (el.offsetParent !== null || el.getClientRects().length > 0);
const boxes = Array.from(document.querySelectorAll('[data-testid="tweetTextarea_0"]'));View on GitHub (pinned to 49907e53dc)
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
Example fix
// before (in-page script)
return { ok: true, id, url: location.pathname };
// after
return { ok: true, id, url: location.origin + location.pathname }; Defensive patterns
Strategy: validation
Validate before calling
let parsed; try { parsed = new URL(result.url); } catch { throw new Error('status url must be an absolute http(s) URL'); }
if (!['https:', 'http:'].includes(parsed.protocol)) throw new Error('status url must be http(s)'); Type guard
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; } } Try / catch
try {
const result = submitTweet(page, text);
} catch (e) {
if (String(e.message).includes('malformed status url')) { /* url was relative or unparseable; capture location.href instead */ }
throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- LinkedIn thread-snapshot blocked: thread_url_mismatch
- ${context} returned a malformed result.
- ${context} returned a malformed message.
- ${context} returned a malformed error.
- Twitter post completion returned only one of id/url.
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/487f6b105ab21b28.
Report an issue: GitHub.