jackwener/OpenCLI · error · CommandExecutionError

Mercury receipt upload did not confirm exactly one uploaded

Error message

Mercury receipt upload did not confirm exactly one uploaded file

What it means

Thrown when the result of `page.uploadFiles(RECEIPT_INPUT_SELECTOR, [input.receipt])` does not confirm a successful upload of exactly one file — i.e. the result is falsy, `uploaded !== true`, or `files !== 1`. The library treats upload as unverified unless the bridge explicitly confirms success, so any mismatch aborts the command.

Source

Thrown at clis/mercury/reimbursement-draft.js:64

        await page.wait({ time: 1 });
        const createSurface = await assertCreateExpenseSurface(page);
        if (createSurface.hasFinalSubmit && /review/i.test(createSurface.bodyPreview || '')) {
            throw new CommandExecutionError('Mercury appears to have an existing expense review open; refusing to click a possible final Submit expense button');
        }

        const opened = await clickCreateExpenseButton(page);
        if (!opened.clicked) {
            throw new CommandExecutionError('Could not find the Mercury Submit expense or New expense button');
        }

        await page.wait({ time: 2 });

        if (!page.uploadFiles) {
            throw new CommandExecutionError('Mercury reimbursement-draft requires Browser Bridge uploadFiles support to verify the intended receipt input');
        }
        const upload = await page.uploadFiles(RECEIPT_INPUT_SELECTOR, [input.receipt]);
        if (!upload || upload.uploaded !== true || upload.files !== 1) {
            throw new CommandExecutionError('Mercury receipt upload did not confirm exactly one uploaded file');
        }
        if (upload.target !== RECEIPT_INPUT_SELECTOR || upload.matches_n !== 1) {
            throw new CommandExecutionError('Mercury receipt upload did not confirm the intended receipt input');
        }
        const uploadedNames = Array.isArray(upload.file_names) ? upload.file_names : [];
        if (!uploadedNames.includes(receiptBasename(input.receipt))) {
            throw new CommandExecutionError('Mercury receipt upload did not confirm the expected receipt file');
        }

        if (input.ocrWaitSeconds > 0) await page.wait({ time: input.ocrWaitSeconds });

        const fields = await fillReimbursementFields(page, input);
        await page.wait({ time: 1 });
        const expectedFields = ['amount', 'currency', 'date', 'merchant', 'category', 'notes'];
        const missing = expectedFields.filter((key) => !fields.touched[key]);
        if (missing.length > 0) {
            throw new CommandExecutionError(`Mercury reimbursement form fields were not all filled: ${missing.join(', ')}`);
        }

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Verify the --receipt path exists and is readable before running the command.
  2. Check Mercury's expense form DOM and update RECEIPT_INPUT_SELECTOR in clis/mercury/utils.js if the input markup changed.
  3. Add or increase the post-dialog wait (currently 2s at line 57) so the file input is rendered before uploadFiles runs.
  4. Re-run with the browser in foreground to watch whether the file chooser opens and the file is actually attached; inspect bridge logs for upload errors.

Example fix

// before
const upload = await page.uploadFiles(RECEIPT_INPUT_SELECTOR, [input.receipt]);
// after (guard before calling)
if (!fs.existsSync(input.receipt)) throw new Error(`receipt not found: ${input.receipt}`);
await page.wait({ time: 4 }); // let the form fully render
const upload = await page.uploadFiles(RECEIPT_INPUT_SELECTOR, [input.receipt]);
Defensive patterns

Strategy: validation

Validate before calling

import fs from 'node:fs';
if (!fs.existsSync(input.receipt)) {
  throw new Error(`receipt file not found: ${input.receipt}`);
}

Type guard

function isConfirmedSingleUpload(upload) {
  return Boolean(upload) && upload.uploaded === true && upload.files === 1;
}

Try / catch

try {
  await opencli(['mercury', 'reimbursement-draft', ...args]);
} catch (e) {
  if (String(e.message).includes('did not confirm exactly one uploaded file')) {
    // verify receipt path + form selector, retry once after wait
  } else throw e;
}

Prevention

When it happens

Trigger: Calling `opencli Mercury reimbursement-draft` where the receipt path is invalid/unreadable, the receipt file input selector no longer matches, the bridge upload dispatches but the page swallows the files, or uploadFiles returns null/undefined on bridge error.

Common situations: Receipt file moved or deleted before the run; Mercury changed its expense form markup so RECEIPT_INPUT_SELECTOR no longer matches a real <input type=file>; Mercury UI update causing the input to be disabled or replaced after the dialog opens; a slow-loading form where the input isn't attached yet.

Related errors


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