jackwener/OpenCLI · error · CommandExecutionError

Twitter post completion returned only one of id/url.

Error message

Twitter post completion returned only one of id/url.

What it means

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).

Source

Thrown at clis/twitter/post.js:60

}

function requirePostActionResult(value, context) {
    const result = unwrapBrowserResult(value);
    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, '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.');

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Retry the post — a transient partial result often resolves once the composer page fully redirects
  2. 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)
  3. Log the full result object in clickResult to see which of id/url was captured
  4. Pin/verify the CLI version matches the current X compose flow

Example fix

// before (in-page script): returns id scraped separately
return { ok: true, id: statusId };
// after: derive both from the redirected URL
const url = location.href;
const m = url.match(/\/status\/(\d+)/);
if (!m) return { ok: false, error: 'no status url after posting' };
return { ok: true, id: m[1], url };
Defensive patterns

Strategy: validation

Validate before calling

function submitPairComplete(r) { const hasId = !!(r && r.id), hasUrl = !!(r && r.url); return hasId === hasUrl; }
if (!submitPairComplete(clickResult)) throw new Error('posting returned incomplete result; retry');

Type guard

function isCompleteSubmitResult(v) { return typeof v === 'object' && v !== null && ('id' in v) === ('url' in v); }

Try / catch

try {
  const result = submitTweet(page, text);
} catch (e) {
  if (String(e.message).includes('only one of id/url')) { /* retry once; likely partial page load */ }
  throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/5d5934c2a6f1d7dd. Report an issue: GitHub.