jackwener/OpenCLI · error · CommandExecutionError
Image upload failed (base64 fallback): ${upload?.error ?? 'u
Error message
Image upload failed (base64 fallback): ${upload?.error ?? 'unknown error'} What it means
attachImagesViaDataTransfer falls back to injecting image Files via a DataTransfer onto the composer's file input when direct paste/drop fails. If that fallback evaluate returns upload.ok === false (or nothing at all), this error is thrown embedding the browser-side failure reason (`upload.error`) or 'unknown error'. It means the image never reached the composer through the base64 fallback path.
Source
Thrown at clis/twitter/post.js:230
try {
Object.defineProperty(input, 'files', { value: dt.files, writable: false, configurable: true });
assigned = input.files && input.files.length >= ${JSON.stringify(absPaths.length)};
} catch(e) {
try {
const nativeInputFileSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'files');
if (nativeInputFileSetter && nativeInputFileSetter.set) {
nativeInputFileSetter.set.call(input, dt.files);
assigned = input.files && input.files.length >= ${JSON.stringify(absPaths.length)};
}
} catch(e2) { /* ignore */ }
}
if (!assigned) return { ok: false, error: 'Could not assign files to input' };
input.dispatchEvent(new Event('change', { bubbles: true }));
input.dispatchEvent(new Event('input', { bubbles: true }));
return { ok: true };
})()`), 'Twitter image upload fallback');
if (!upload?.ok) {
throw new CommandExecutionError(`Image upload failed (base64 fallback): ${upload?.error ?? 'unknown error'}`);
}
}
async function submitTweet(page, text) {
const clickResult = requirePostActionResult(await page.evaluate(`(async () => {
try {
const visible = (el) => !!el && (el.offsetParent !== null || el.getClientRects().length > 0);
for (const toast of Array.from(document.querySelectorAll('[role="alert"], [data-testid="toast"]'))) {
if (visible(toast)) toast.setAttribute('data-opencli-before-submit-toast', 'true');
}
const buttons = Array.from(document.querySelectorAll('[data-testid="tweetButtonInline"], [data-testid="tweetButton"]'));
const btn = buttons.find((el) => visible(el) && !el.disabled && el.getAttribute('aria-disabled') !== 'true');
if (!btn) return { ok: false, message: 'Tweet button is disabled or not found.' };
btn.click();
return { ok: true };
} catch (e) { return { ok: false, message: String(e) }; }
})()`), 'Twitter post click');
if (!clickResult?.ok) return clickResult;View on GitHub (pinned to 49907e53dc)
Solutions
- Read the `upload.error` text in the message — 'Could not assign files to input' means update the input selector in attachImagesViaDataTransfer to match the current X DOM
- Confirm the images are supported types (jpg/png/gif/webp) and within the 4-image limit before attaching (validateImagePaths already enforces paths, verify content too)
- Run on a real compose/post page with a loaded session so a file input exists
- Upgrade the browser/automation driver if DataTransfer File assignment is unsupported
Example fix
// before: single selector, fails silently when X changes DOM
const input = document.querySelector('input[data-testid="fileInput"]');
// after: search broadly and report why nothing was found
const input = document.querySelector('input[type="file"][accept*="image"]');
if (!input) return { ok: false, error: 'no file input present on compose page' }; Defensive patterns
Strategy: fallback
Validate before calling
const allowed = ['image/jpeg','image/png','image/gif','image/webp'];
for (const p of paths) { const t = mimeOf(p); if (!allowed.includes(t)) throw new Error(`unsupported image type: ${p}`); }
if (paths.length > 4) throw new Error('max 4 images per tweet'); Type guard
function canAttemptUpload(page) { return page != null && typeof page.evaluate === 'function'; } Try / catch
try {
await attachImagesViaDataTransfer(page, files);
} catch (e) {
if (String(e.message).startsWith('Image upload failed (base64 fallback)')) { console.error('fallback upload failed:', e.message, '- update input selector or check image types'); }
throw e;
} Prevention
- Use only jpg/png/gif/webp images within the 4-image limit
- Ensure the page is on the compose screen with the file input present
- Update the input[type=file] selector whenever X changes its DOM
- Use a modern browser/driver that supports DataTransfer File assignment
When it happens
Trigger: The in-page fallback script fails with 'Could not assign files to input' (no visible file input found, or DataTransfer constructor unavailable), files rejected by the input, or the change/input events dispatched but the composer didn't accept them — all surfaced here via requirePostActionResult(upload, 'Twitter image upload fallback').
Common situations: X removed/renamed the hidden input[type=file] the fallback targets; browser or headless environment lacking DataTransfer/File support (older drivers); images of unsupported type/size silently rejected; page not on the compose screen so no input exists to assign.
Related errors
- ${context} returned a malformed result.
- ${context} returned a malformed message.
- ${context} returned a malformed error.
- Twitter post completion returned only one of id/url.
- Twitter post completion returned a malformed status id.
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/a63a41c21ade50d8.
Report an issue: GitHub.