jackwener/OpenCLI · error · CommandExecutionError
Twitter reply completion returned a malformed status url.
Error message
Twitter reply completion returned a malformed status url.
What it means
validateReplyStatusUrl parses the status URL reported after a reply submit with `new URL(result.url)` and rejects it if parsing throws. X sometimes reports a success permalink that is not an absolute, parseable URL (relative path, bare ID, or truncated string). The library throws this CommandExecutionError so callers know the reply may have posted but confirmation is unreliable.
Source
Thrown at clis/twitter/reply.js:51
if (!result || typeof result !== 'object' || Array.isArray(result) || typeof result.ok !== 'boolean') {
throw new CommandExecutionError(`${context} returned a malformed result.`);
}
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, 'url') && result.url != null && typeof result.url !== 'string') {
throw new CommandExecutionError(`${context} returned a malformed status url.`);
}
return result;
}
function validateReplyStatusUrl(result) {
if (!result.url) return;
let url;
try {
url = new URL(result.url);
} catch {
throw new CommandExecutionError('Twitter reply 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) {
throw new CommandExecutionError('Twitter reply completion returned a malformed status url.');
}
}
async function openReplyComposer(page, rawUrl) {
await page.goto(buildReplyComposerUrl(rawUrl), { waitUntil: 'load', settleMs: 2500 });
try {
await page.wait({ selector: COMPOSER_SELECTOR, timeout: 15 });
return { ok: true };
} catch {
// X sometimes leaves /compose/post?in_reply_to=<id> on the Home
// timeline behind a loading dialog. Fall back to the canonical tweet
// page and click the visible Reply action there.
await page.goto(rawUrl, { waitUntil: 'load', settleMs: 2500 });View on GitHub (pinned to 49907e53dc)
Solutions
- In the page script, always return an absolute URL: `url: location.origin + element.getAttribute('href')` or `element.href`.
- Prefix relative values before parsing: `new URL(result.url, 'https://x.com')` in validateReplyStatusUrl.
- Re-run the reply command — a transient race can yield a partial permalink; check the profile to see whether the reply actually posted before retrying.
Example fix
// before
try { url = new URL(result.url); } catch { throw ... }
// after
try { url = new URL(result.url, 'https://x.com'); } catch { throw ... } Defensive patterns
Strategy: validation
Validate before calling
function isParseableUrl(u) {
try { new URL(u); return true; } catch { return false; }
}
// precondition on reported permalink:
if (result.url && !isParseableUrl(result.url)) {
// normalize before calling the command or treat confirmation as failed
result.url = new URL(result.url, 'https://x.com').href;
} Type guard
function isAbsoluteHttpUrl(u) {
if (typeof u !== 'string') return false;
try { const url = new URL(u); return url.protocol === 'https:'; } catch { return false; }
} Try / catch
try {
await cli('twitter', 'reply', { url, text });
} catch (e) {
if (String(e.message).includes('malformed status url')) {
// reply may have posted; verify on profile before retrying
} else throw e;
} Prevention
- Always return absolute permalinks (origin + path) from in-page detection code.
- Wrap relative paths with a base when constructing the reported URL.
- Re-read the permalink after a short settle delay to avoid racing a half-rendered element.
When it happens
Trigger: The page's post-submit detection returns a url like `/username/status/123` (relative), `1234567890` (bare ID), an empty-ish template string, or any value that fails `new URL()` construction.
Common situations: X DOM changes so the script scrapes an href fragment instead of the full permalink; an in-page snippet returning `element.pathname` instead of `element.href`; race conditions reading a not-yet-populated permalink element.
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
- Unexpected 12306 probe: ${JSON.stringify(probe)}
- Waiting for 12306 tk auth cookie
- Instagram media metadata returned an invalid download URL fo
- Instagram media metadata returned an unsupported download UR
- x.com
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/821fa8f9991df3bf.
Report an issue: GitHub.