{"record":{"id":"a63a41c21ade50d8","repo":"jackwener/OpenCLI","slug":"image-upload-failed-base64-fallback-upload-e","errorCode":null,"errorMessage":"Image upload failed (base64 fallback): ${upload?.error ?? 'unknown error'}","messagePattern":"Image upload failed \\(base64 fallback\\): (.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/twitter/post.js","lineNumber":230,"sourceCode":"        try {\n            Object.defineProperty(input, 'files', { value: dt.files, writable: false, configurable: true });\n            assigned = input.files && input.files.length >= ${JSON.stringify(absPaths.length)};\n        } catch(e) {\n            try {\n                const nativeInputFileSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'files');\n                if (nativeInputFileSetter && nativeInputFileSetter.set) {\n                    nativeInputFileSetter.set.call(input, dt.files);\n                    assigned = input.files && input.files.length >= ${JSON.stringify(absPaths.length)};\n                }\n            } catch(e2) { /* ignore */ }\n        }\n        if (!assigned) return { ok: false, error: 'Could not assign files to input' };\n        input.dispatchEvent(new Event('change', { bubbles: true }));\n        input.dispatchEvent(new Event('input', { bubbles: true }));\n        return { ok: true };\n    })()`), 'Twitter image upload fallback');\n    if (!upload?.ok) {\n        throw new CommandExecutionError(`Image upload failed (base64 fallback): ${upload?.error ?? 'unknown error'}`);\n    }\n}\n\nasync function submitTweet(page, text) {\n    const clickResult = requirePostActionResult(await page.evaluate(`(async () => {\n        try {\n            const visible = (el) => !!el && (el.offsetParent !== null || el.getClientRects().length > 0);\n            for (const toast of Array.from(document.querySelectorAll('[role=\"alert\"], [data-testid=\"toast\"]'))) {\n                if (visible(toast)) toast.setAttribute('data-opencli-before-submit-toast', 'true');\n            }\n            const buttons = Array.from(document.querySelectorAll('[data-testid=\"tweetButtonInline\"], [data-testid=\"tweetButton\"]'));\n            const btn = buttons.find((el) => visible(el) && !el.disabled && el.getAttribute('aria-disabled') !== 'true');\n            if (!btn) return { ok: false, message: 'Tweet button is disabled or not found.' };\n            btn.click();\n            return { ok: true };\n        } catch (e) { return { ok: false, message: String(e) }; }\n    })()`), 'Twitter post click');\n    if (!clickResult?.ok) return clickResult;","sourceCodeStart":212,"sourceCodeEnd":248,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/twitter/post.js#L212-L248","documentation":"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.","triggerScenarios":"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').","commonSituations":"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.","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"],"exampleFix":"// before: single selector, fails silently when X changes DOM\nconst input = document.querySelector('input[data-testid=\"fileInput\"]');\n// after: search broadly and report why nothing was found\nconst input = document.querySelector('input[type=\"file\"][accept*=\"image\"]');\nif (!input) return { ok: false, error: 'no file input present on compose page' };","handlingStrategy":"fallback","validationCode":"const allowed = ['image/jpeg','image/png','image/gif','image/webp'];\nfor (const p of paths) { const t = mimeOf(p); if (!allowed.includes(t)) throw new Error(`unsupported image type: ${p}`); }\nif (paths.length > 4) throw new Error('max 4 images per tweet');","typeGuard":"function canAttemptUpload(page) { return page != null && typeof page.evaluate === 'function'; }","tryCatchPattern":"try {\n  await attachImagesViaDataTransfer(page, files);\n} catch (e) {\n  if (String(e.message).startsWith('Image upload failed (base64 fallback)')) { console.error('fallback upload failed:', e.message, '- update input selector or check image types'); }\n  throw e;\n}","preventionTips":["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"],"tags":["browser-automation","image-upload","x-twitter"],"backgroundTag":"image-upload-failed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}