{"record":{"id":"d4ee3bb7578afb43","repo":"jackwener/OpenCLI","slug":"mercury-receipt-upload-did-not-confirm-the-expecte","errorCode":null,"errorMessage":"Mercury receipt upload did not confirm the expected receipt file","messagePattern":"Mercury receipt upload did not confirm the expected receipt file","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/mercury/reimbursement-draft.js","lineNumber":71,"sourceCode":"        if (!opened.clicked) {\n            throw new CommandExecutionError('Could not find the Mercury Submit expense or New expense button');\n        }\n\n        await page.wait({ time: 2 });\n\n        if (!page.uploadFiles) {\n            throw new CommandExecutionError('Mercury reimbursement-draft requires Browser Bridge uploadFiles support to verify the intended receipt input');\n        }\n        const upload = await page.uploadFiles(RECEIPT_INPUT_SELECTOR, [input.receipt]);\n        if (!upload || upload.uploaded !== true || upload.files !== 1) {\n            throw new CommandExecutionError('Mercury receipt upload did not confirm exactly one uploaded file');\n        }\n        if (upload.target !== RECEIPT_INPUT_SELECTOR || upload.matches_n !== 1) {\n            throw new CommandExecutionError('Mercury receipt upload did not confirm the intended receipt input');\n        }\n        const uploadedNames = Array.isArray(upload.file_names) ? upload.file_names : [];\n        if (!uploadedNames.includes(receiptBasename(input.receipt))) {\n            throw new CommandExecutionError('Mercury receipt upload did not confirm the expected receipt file');\n        }\n\n        if (input.ocrWaitSeconds > 0) await page.wait({ time: input.ocrWaitSeconds });\n\n        const fields = await fillReimbursementFields(page, input);\n        await page.wait({ time: 1 });\n        const expectedFields = ['amount', 'currency', 'date', 'merchant', 'category', 'notes'];\n        const missing = expectedFields.filter((key) => !fields.touched[key]);\n        if (missing.length > 0) {\n            throw new CommandExecutionError(`Mercury reimbursement form fields were not all filled: ${missing.join(', ')}`);\n        }\n\n        const reviewClick = await page.evaluate(`(() => {\n            const norm = (s) => String(s || '').replace(/\\\\s+/g, ' ').trim().toLowerCase();\n            const candidates = Array.from(document.querySelectorAll('button, a, [role=\"button\"], [role=\"link\"]'))\n              .filter((node) => {\n                const style = window.getComputedStyle(node);\n                return style.visibility !== 'hidden' && style.display !== 'none' && node.offsetParent !== null;","sourceCodeStart":53,"sourceCodeEnd":89,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/mercury/reimbursement-draft.js#L53-L89","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Pass an absolute, stable path to --receipt and confirm the basename matches what the browser will report.","Check `upload.file_names` in debug output and compare against `receiptBasename(input.receipt)` to see what actually got uploaded.","Ensure the Browser Bridge runs on the same host/filesystem as the CLI so the exact file is accessible and uploaded unrenamed.","If Mercury/bridge sanitizes filenames, relax the comparison to a normalized match in utils.js (lowercase, trim)."],"exampleFix":"// before\nopencli mercury reimbursement-draft --receipt receipt.png ...\n// after\nopencli mercury reimbursement-draft --receipt /abs/path/receipt.png ...","handlingStrategy":"validation","validationCode":"import path from 'node:path';\nimport fs from 'node:fs';\nconst abs = path.resolve(input.receipt);\nif (!fs.existsSync(abs)) throw new Error(`receipt not found: ${abs}`);\nconst expected = path.basename(abs);\n// after upload: if (!upload.file_names?.includes(expected)) throw ...","typeGuard":"function uploadedExpectedFile(upload, receiptPath) {\n  const names = Array.isArray(upload?.file_names) ? upload.file_names : [];\n  return names.includes(path.basename(receiptPath));\n}","tryCatchPattern":"try {\n  await uploadReceipt(page, selector, receiptPath);\n} catch (e) {\n  if (String(e.message).includes('did not confirm the expected receipt file')) {\n    // compare upload.file_names vs basename(receiptPath), fix path, retry\n  } else throw e;\n}","preventionTips":["Pass absolute receipt paths so the bridge uploads the exact file.","Avoid symlinks/temp files with volatile names for receipts.","Ensure the Browser Bridge daemon shares the same filesystem as the CLI.","Log upload.file_names in debug runs to catch renaming early."],"tags":["file-upload","path-resolution","browser-automation","mercury"],"backgroundTag":"uploaded-file-mismatch","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}