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
- Verify the --receipt path exists and is readable before running the command.
- Check Mercury's expense form DOM and update RECEIPT_INPUT_SELECTOR in clis/mercury/utils.js if the input markup changed.
- Add or increase the post-dialog wait (currently 2s at line 57) so the file input is rendered before uploadFiles runs.
- 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
- Always pass an existing, readable absolute receipt path.
- Update RECEIPT_INPUT_SELECTOR whenever Mercury changes its expense form markup.
- Allow the expense dialog to fully render (wait) before upload runs.
- Inspect uploadFiles return shape in a debug run to confirm the bridge contract.
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
- Mercury receipt upload did not confirm the intended receipt
- Mercury reimbursement-draft requires Browser Bridge uploadFi
- Mercury receipt upload did not confirm the expected receipt
- Booking.com page declared results but no property cards were
- Failed to attach file
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/314af7d72cece474.
Report an issue: GitHub.