jackwener/OpenCLI · error · CommandExecutionError

Tweet failed to post.

Error message

Tweet failed to post.

What it means

CommandExecutionError thrown when submitTweet explicitly reports failure ({ ok: false }) — unlike the unconfirmed timeout case, this is a definite failure and the message states nothing was posted, so retrying is safe.

Source

Thrown at clis/twitter/post.js:355

        }

        // Insert and verify the text after media upload so text + images are in
        // the final Draft.js composer state immediately before clicking Post.
        const typeResult = await insertComposerText(page, text);
        if (!typeResult?.ok) {
            throw new CommandExecutionError(typeResult?.message ?? 'Could not type tweet text.', 'Open the composer in the browser and check whether X is asking you to log in.');
        }

        await page.wait(1);
        const result = await submitTweet(page, text);
        if (result?.unconfirmed) {
            // The poll expiring does not mean the tweet stayed in the composer,
            // so this must not read as a definite failure: the agent workflow
            // retries CommandExecutionError and would post twice (#2255).
            throw new TimeoutError('twitter post', SUBMIT_TIMEOUT_MS / 1000, `${result.message} Check \`opencli twitter tweets --limit 1\` before retrying; the post may already be live.`);
        }
        if (!result?.ok) {
            throw new CommandExecutionError(result?.message ?? 'Tweet failed to post.', 'Nothing was posted. Open the composer in the browser and retry.');
        }
        return [{
            status: 'success',
            message: result.message,
            text,
            ...(result.id ? { id: result.id } : {}),
            ...(result.url ? { url: result.url } : {}),
        }];
    }
});

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Read result?.message for X's specific reason (e.g. duplicate status, rate limit)
  2. If duplicate content, edit the tweet text and retry
  3. If rate-limited, wait and retry later
  4. Open the composer in the browser and post manually to see X's on-page error
  5. Check the account isn't locked/limited; verify the composer flow still works after X deploys
Defensive patterns

Strategy: try-catch

Validate before calling

// avoid X duplicate rejection before posting
const recent = await opencli('twitter tweets', { limit: 1 });
if (recent?.[0]?.text === text) throw new Error('tweet text identical to last post');
if (!text.trim()) throw new Error('tweet text is empty');

Try / catch

try {
  await postTweet({ text });
} catch (e) {
  if (e.name === 'CommandExecutionError' && /failed to post/i.test(e.message)) {
    console.error('Post definitively failed (nothing posted):', e.message);
    // safe to fix the cause and retry
  } else throw e;
}

Prevention

When it happens

Trigger: submitTweet resolves with result.ok falsy and no unconfirmed flag: the Post button click failed, X returned an error (duplicate post, empty tweet, rate limit, account restriction), or result is undefined/null.

Common situations: Tweeting text identical to a recent tweet (X duplicate detection); account temporarily limited from posting; X frontend changes breaking the submit flow; composer discarded the text.

Related errors


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