jackwener/OpenCLI · error · Error
Image upload failed: ${upload?.error ?? 'unknown error'}
Error message
Image upload failed: ${upload?.error ?? 'unknown error'} What it means
Thrown by attachComposerImage after the base64 DataTransfer fallback path runs inside page.evaluate. When the in-page IIFE returns { ok: false, error } — or nothing at all — the error is surfaced as `Image upload failed: ${upload?.error ?? 'unknown error'}`. Known in-page errors are 'No file input found on page' and 'Could not assign files to input'.
Source
Thrown at clis/twitter/utils.js:193
assigned = input.files && input.files.length > 0;
} catch(e) {
// files property not redefinable — use nativeInputValueSetter trick
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 > 0;
}
} 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 };
})()
`);
if (!upload?.ok) {
throw new Error(`Image upload failed: ${upload?.error ?? 'unknown error'}`);
}
}
const uploadState = await waitForComposerMediaReady(page, 1);
if (!uploadState?.ok) {
throw new Error(uploadState?.message ?? 'Image upload failed: preview did not appear.');
}
}
export function isRecoverableFileInputError(error) {
const msg = error instanceof Error ? error.message : String(error);
return /unknown action|not supported|not[-\s]?allowed|notallowederror/i.test(msg);
}
export async function waitForComposerMediaReady(page, expectedCount = 1) {
const iterations = Math.ceil(MEDIA_UPLOAD_TIMEOUT_MS / MEDIA_UPLOAD_POLL_MS);
return page.evaluate(`
(async () => {
const expected = ${JSON.stringify(expectedCount)};View on GitHub (pinned to 49907e53dc)
Solutions
- Update the OpenCLI browser extension to v1.6+ so the reliable CDP setFileInput path is used instead of the base64 fallback
- Verify the composer page is fully loaded and the file input selector still matches the current X DOM; update fileInputSelector if it changed
- Compress the image below ~500KB base64 (the code warns above that size) and retry the fallback
- Retry once after reloading the page — transient hydration/RAF timing can make the input briefly unassignable
Example fix
// before
await attachComposerImage(page, '/tmp/big.png'); // fallback path, 'Could not assign files to input'
// after
// compress first so base64 is small and update the extension
execSync('convert /tmp/big.png -resize 1600x /tmp/small.jpg');
await attachComposerImage(page, '/tmp/small.jpg'); Defensive patterns
Strategy: fallback
Validate before calling
const hasInput = await page.evaluate(`!!document.querySelector(${JSON.stringify(COMPOSER_FILE_INPUT_SELECTOR)})`);
if (!hasInput) throw new Error('Composer file input not present — open/reload the compose page first'); Type guard
function uploadResultOk(upload) {
return Boolean(upload && typeof upload === 'object' && upload.ok === true);
} Try / catch
try {
await attachComposerImage(page, absPath);
} catch (err) {
if (err.message === 'Image upload failed: No file input found on page') {
await page.reload(); // then retry
} else if (err.message === 'Image upload failed: Could not assign files to input') {
console.error('Base64 fallback blocked; install/update the browser extension (v1.6+) for CDP upload');
} else { throw err; }
} Prevention
- Use extension v1.6+ so the CDP path runs instead of the fragile base64 fallback
- Verify the composer input selector against the live DOM before automating
- Keep base64 payload small (<~500KB) — compress images before the fallback path
- Test the fallback path after any X UI redesign
When it happens
Trigger: The fallback path executes because setFileInput was missing or recoverably failed, and then either document.querySelector(fileInputSelector) finds no input, or the File/DataTransfer assignment fails because the input's `files` property cannot be redefined and the nativeInputValueSetter trick also fails.
Common situations: Hardened pages or CSP/DOM setups where HTMLInputElement.prototype.files cannot be patched and Object.defineProperty on the input is blocked; X changing the composer markup so the default selector no longer matches; running without the CDP-capable extension so only the fragile fallback path is used; very large images making the evaluate payload unreliable.
Related errors
- Image upload failed: ${msg}
- Image upload failed: preview did not appear.
- Failed to extract Booking.com cards: ${err?.message || err}
- coupang add-to-cart evaluation failed: ${error?.message || e
- coupang search
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/80abf672a6565666.
Report an issue: GitHub.