{"record":{"id":"314af7d72cece474","repo":"jackwener/OpenCLI","slug":"mercury-receipt-upload-did-not-confirm-exactly-one","errorCode":null,"errorMessage":"Mercury receipt upload did not confirm exactly one uploaded file","messagePattern":"Mercury receipt upload did not confirm exactly one uploaded file","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/mercury/reimbursement-draft.js","lineNumber":64,"sourceCode":"        await page.wait({ time: 1 });\n        const createSurface = await assertCreateExpenseSurface(page);\n        if (createSurface.hasFinalSubmit && /review/i.test(createSurface.bodyPreview || '')) {\n            throw new CommandExecutionError('Mercury appears to have an existing expense review open; refusing to click a possible final Submit expense button');\n        }\n\n        const opened = await clickCreateExpenseButton(page);\n        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        }","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/mercury/reimbursement-draft.js#L46-L82","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nconst upload = await page.uploadFiles(RECEIPT_INPUT_SELECTOR, [input.receipt]);\n// after (guard before calling)\nif (!fs.existsSync(input.receipt)) throw new Error(`receipt not found: ${input.receipt}`);\nawait page.wait({ time: 4 }); // let the form fully render\nconst upload = await page.uploadFiles(RECEIPT_INPUT_SELECTOR, [input.receipt]);","handlingStrategy":"validation","validationCode":"import fs from 'node:fs';\nif (!fs.existsSync(input.receipt)) {\n  throw new Error(`receipt file not found: ${input.receipt}`);\n}","typeGuard":"function isConfirmedSingleUpload(upload) {\n  return Boolean(upload) && upload.uploaded === true && upload.files === 1;\n}","tryCatchPattern":"try {\n  await opencli(['mercury', 'reimbursement-draft', ...args]);\n} catch (e) {\n  if (String(e.message).includes('did not confirm exactly one uploaded file')) {\n    // verify receipt path + form selector, retry once after wait\n  } else throw e;\n}","preventionTips":["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."],"tags":["file-upload","browser-automation","selector-mismatch","mercury"],"backgroundTag":"file-upload-not-confirmed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}