jackwener/OpenCLI · error · TimeoutError
twitter image upload
Error message
twitter image upload
What it means
TimeoutError thrown when the images attached to a tweet composer never finish uploading within UPLOAD_TIMEOUT_MS. waitForImageUpload polls the composer until the expected number of uploaded media thumbnails appear; if it can't confirm them, the command aborts with a message noting nothing was posted so users know a retry is safe.
Source
Thrown at clis/twitter/post.js:335
// Attach media before inserting text. Uploading media after Draft.js has
// text can re-render/reset the editor, causing image-only posts.
if (absPaths.length > 0) {
await page.wait({ selector: FILE_INPUT_SELECTOR, timeout: 20 });
if (page.setFileInput) {
try {
await page.setFileInput(absPaths, FILE_INPUT_SELECTOR);
} catch (err) {
if (!isRecoverableFileInputError(err)) {
throw err;
}
await attachImagesViaDataTransfer(page, absPaths);
}
} else {
await attachImagesViaDataTransfer(page, absPaths);
}
const uploadState = await waitForImageUpload(page, absPaths.length);
if (!uploadState?.ok) {
throw new TimeoutError('twitter image upload', UPLOAD_TIMEOUT_MS / 1000, 'Nothing was posted. Retry, or attach a smaller image.');
}
}
// 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.`);
}View on GitHub (pinned to 49907e53dc)
Solutions
- Retry the command; transient upload slowness is the most common cause
- Attach smaller or fewer images (compress to under a few MB)
- Verify the image formats are jpg/png/gif/webp and readable by the browser session
- Check the browser session is healthy and logged in; re-run `opencli login` if the composer looked broken
- Pin/report if X changed the composer DOM so waitForImageUpload's selectors no longer match
Example fix
// before
await opencli('twitter post', { text: 'hi', images: 'huge-20mb.png' });
// after
const small = await compress('huge-20mb.png', { maxWidth: 1600 });
await opencli('twitter post', { text: 'hi', images: small }); Defensive patterns
Strategy: retry
Validate before calling
const files = images.split(',').map(s => s.trim());
const stat = await fs.stat(f);
if (!/\.(jpe?g|png|gif|webp)$/i.test(f)) throw new Error('unsupported format: ' + f);
if (stat.size > 5 * 1024 * 1024) throw new Error('image too large: ' + f); Try / catch
const MAX_UPLOAD_ATTEMPTS = 2;
for (let i = 0; i < MAX_UPLOAD_ATTEMPTS; i++) {
try { await postTweet({ text, images }); break; }
catch (e) {
if (e.name === 'TimeoutError' && e.message.includes('twitter image upload') && i < MAX_ATTEMPTS - 1) continue;
throw e;
}
} Prevention
- Compress images below ~5MB before attaching
- Stick to jpg/png and standard dimensions
- Retry once on TimeoutError before escalating
- Keep the browser session fast/logged in so X's media pipeline responds
When it happens
Trigger: Calling `opencli twitter post` with --images where setFileInput or the DataTransfer fallback attached files but the composer never shows the expected number of uploaded thumbnails before the timeout: slow network, oversized image, unsupported format slipping through, or X's media pipeline failing silently.
Common situations: Uploading a multi-MB photo on a slow connection; attaching a 4-image set where one fails; X A/B UI changes hiding the upload progress indicator; animated GIFs that X re-encodes slowly.
Related errors
- Unexpected 12306 probe: ${JSON.stringify(probe)}
- twitter follow confirmation
- twitter followers API capture
- twitter like confirmation
- ${result.message} Check muted words before retrying; the wor
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/29ef26838e8bfb87.
Report an issue: GitHub.