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

  1. Update the OpenCLI browser extension to v1.6+ so the reliable CDP setFileInput path is used instead of the base64 fallback
  2. Verify the composer page is fully loaded and the file input selector still matches the current X DOM; update fileInputSelector if it changed
  3. Compress the image below ~500KB base64 (the code warns above that size) and retry the fallback
  4. 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

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


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