{"record":{"id":"365064ca066ae7e3","repo":"jackwener/OpenCLI","slug":"mercury-is-already-showing-a-submit-review-surface","errorCode":null,"errorMessage":"Mercury is already showing a submit/review surface; refusing to click a possible final Submit expense button","messagePattern":"Mercury is already showing a submit/review surface; refusing to click a possible final Submit expense button","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/mercury/utils.js","lineNumber":197,"sourceCode":"        const dangerous = candidates.find((node) => {\n          const container = node.closest('[role=\"dialog\"], dialog, form, [aria-modal=\"true\"]');\n          const context = String(container?.innerText || container?.textContent || '').replace(/\\\\s+/g, ' ').trim();\n          return Boolean(container) || /Review|receipt|amount|merchant|category|notes|expense date/i.test(context);\n        });\n        if (dangerous) {\n          return { clicked: false, blocked: true, text: dangerous.innerText || dangerous.textContent || '' };\n        }\n        const el = candidates[0];\n        if (!el) return { clicked: false, blocked: false };\n        el.click();\n        return { clicked: true, blocked: false, text: el.innerText || el.textContent || '' };\n    })()`);\n    const payload = assertObject(result, 'create expense click result');\n    if (typeof payload.clicked !== 'boolean' || typeof payload.blocked !== 'boolean') {\n        throw new CommandExecutionError('Mercury returned malformed create expense click result');\n    }\n    if (payload.blocked) {\n        throw new CommandExecutionError('Mercury is already showing a submit/review surface; refusing to click a possible final Submit expense button');\n    }\n    return payload;\n}\n\nexport async function assertCreateExpenseSurface(page) {\n    const state = await page.evaluate(`(() => {\n        const text = document.body?.innerText || '';\n        return {\n            url: location.href,\n            title: document.title,\n            hasFinalSubmit: /Submit expense/i.test(text) && /Review|receipt|amount|merchant|category|notes/i.test(text),\n            hasForm: /receipt|amount|merchant|category|notes|expense date/i.test(text),\n            bodyPreview: text.replace(/\\\\s+/g, ' ').trim().slice(0, 600)\n        };\n    })()`);\n    const payload = assertBooleanFields(assertMercuryState(state, 'expense form state'), 'expense form state', ['hasFinalSubmit', 'hasForm']);\n    if (payload.hasFinalSubmit && !payload.hasForm) {\n        throw new CommandExecutionError('Mercury is showing a submit button without a recognizable expense form; refusing to click');","sourceCodeStart":179,"sourceCodeEnd":215,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/mercury/utils.js#L179-L215","documentation":"This is an intentional safety stop in clickCreateExpenseButton(). The injected script detects when the only 'Submit expense'-matching button lives inside a dialog/form/review context — i.e. Mercury is already showing a final Submit expense (review) surface. Clicking again could resubmit or create a duplicate expense, so the library refuses and throws instead of clicking.","triggerScenarios":"clickCreateExpenseButton() is called while the page already shows a submit/review dialog: the injected script's `dangerous` check finds a matching button inside [role=\"dialog\"], dialog, form, or [aria-modal=\"true\"], or the container text matches /Review|receipt|amount|merchant|category|notes|expense date/i, setting blocked=true.","commonSituations":"Calling the create-expense flow twice in one session after the expense form was already submitted; retry logic re-invoking clickCreateExpenseButton after a timeout while the review screen is open; resuming an automation run mid-flow.","solutions":["Do not retry the click; treat the expense as already at the review stage and assert the surface with assertCreateExpenseSurface().","Navigate back to the expenses list (inspectMercury / MERCURY_EXPENSES_URL) before attempting to open the create-expense form again.","Guard the caller (`opened`) so clickCreateExpenseButton only runs once per session, e.g. a `createSurface` state flag.","If a duplicate flow is needed, start a fresh page/session."],"exampleFix":"// before\nawait clickCreateExpenseButton(page); // may throw if review surface already open\n// after\nconst state = await assertCreateExpenseSurface(page);\nif (!state.hasFinalSubmit) {\n  await clickCreateExpenseButton(page);\n}","handlingStrategy":"validation","validationCode":"const state = await assertCreateExpenseSurface(page);\nif (!state.hasFinalSubmit) {\n  await clickCreateExpenseButton(page);\n}","typeGuard":null,"tryCatchPattern":"try {\n  await clickCreateExpenseButton(page);\n} catch (err) {\n  if (err instanceof CommandExecutionError && /refusing to click a possible final Submit/.test(err.message)) {\n    // expense surface already open — skip instead of retrying\n    return { skipped: true };\n  }\n  throw err;\n}","preventionTips":["Track flow state (a `createSurface` flag) so the button is clicked at most once per session.","Never blindly retry after this error — the guard exists to prevent duplicate expenses.","Re-run inspectMercury/assertCreateExpenseSurface after any navigation before clicking.","Treat the error as informational: the expense form is already open or submitted."],"tags":["safety-guard","duplicate-submission","browser-automation","mercury"],"backgroundTag":"unsafe-click-refused","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}