jackwener/OpenCLI · error · CommandExecutionError

Mercury receipt upload did not confirm the expected receipt

Error message

Mercury receipt upload did not confirm the expected receipt file

What it means

Thrown when the bridge's upload result `file_names` array does not contain the basename of the supplied receipt path, meaning the file that was actually attached is not the receipt the user passed via --receipt. This guards against the wrong file (or a placeholder/stale file) being attached to the Mercury expense.

Source

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

        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(', ')}`);
        }

        const reviewClick = await page.evaluate(`(() => {
            const norm = (s) => String(s || '').replace(/\\s+/g, ' ').trim().toLowerCase();
            const candidates = Array.from(document.querySelectorAll('button, a, [role="button"], [role="link"]'))
              .filter((node) => {
                const style = window.getComputedStyle(node);
                return style.visibility !== 'hidden' && style.display !== 'none' && node.offsetParent !== null;

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Pass an absolute, stable path to --receipt and confirm the basename matches what the browser will report.
  2. Check `upload.file_names` in debug output and compare against `receiptBasename(input.receipt)` to see what actually got uploaded.
  3. Ensure the Browser Bridge runs on the same host/filesystem as the CLI so the exact file is accessible and uploaded unrenamed.
  4. If Mercury/bridge sanitizes filenames, relax the comparison to a normalized match in utils.js (lowercase, trim).

Example fix

// before
opencli mercury reimbursement-draft --receipt receipt.png ...
// after
opencli mercury reimbursement-draft --receipt /abs/path/receipt.png ...
Defensive patterns

Strategy: validation

Validate before calling

import path from 'node:path';
import fs from 'node:fs';
const abs = path.resolve(input.receipt);
if (!fs.existsSync(abs)) throw new Error(`receipt not found: ${abs}`);
const expected = path.basename(abs);
// after upload: if (!upload.file_names?.includes(expected)) throw ...

Type guard

function uploadedExpectedFile(upload, receiptPath) {
  const names = Array.isArray(upload?.file_names) ? upload.file_names : [];
  return names.includes(path.basename(receiptPath));
}

Try / catch

try {
  await uploadReceipt(page, selector, receiptPath);
} catch (e) {
  if (String(e.message).includes('did not confirm the expected receipt file')) {
    // compare upload.file_names vs basename(receiptPath), fix path, retry
  } else throw e;
}

Prevention

When it happens

Trigger: Running reimbursement-draft where the browser normalizes/renames the uploaded file, the receipt path contains characters the bridge mangles, or uploadFiles attached a different/cached file than the one given (e.g. path resolution mismatch between CLI cwd and browser bridge host).

Common situations: Receipt passed via relative path while the Browser Bridge daemon runs from a different working directory so it uploads a same-named-but-different file; symlinked or temp-renamed receipt (e.g. /tmp/receipt.png vs uploaded name); OS auto-renaming duplicates like receipt (1).png.

Related errors


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