jackwener/OpenCLI · error · CommandExecutionError
Mercury is showing a submit button without a recognizable ex
Error message
Mercury is showing a submit button without a recognizable expense form; refusing to click
What it means
assertCreateExpenseSurface() checks the page text for a final 'Submit expense' button (hasFinalSubmit) and for a recognizable expense form (hasForm: receipt/amount/merchant/category/notes/expense date). If it sees a submit button but no recognizable form, the library refuses to proceed — the click could be a dangerous final submit on an unexpected page rather than part of a normal expense form.
Source
Thrown at clis/mercury/utils.js:215
throw new CommandExecutionError('Mercury is already showing a submit/review surface; refusing to click a possible final Submit expense button');
}
return payload;
}
export async function assertCreateExpenseSurface(page) {
const state = await page.evaluate(`(() => {
const text = document.body?.innerText || '';
return {
url: location.href,
title: document.title,
hasFinalSubmit: /Submit expense/i.test(text) && /Review|receipt|amount|merchant|category|notes/i.test(text),
hasForm: /receipt|amount|merchant|category|notes|expense date/i.test(text),
bodyPreview: text.replace(/\\s+/g, ' ').trim().slice(0, 600)
};
})()`);
const payload = assertBooleanFields(assertMercuryState(state, 'expense form state'), 'expense form state', ['hasFinalSubmit', 'hasForm']);
if (payload.hasFinalSubmit && !payload.hasForm) {
throw new CommandExecutionError('Mercury is showing a submit button without a recognizable expense form; refusing to click');
}
return payload;
}
export async function fillReimbursementFields(page, input) {
const result = await page.evaluate(`(() => {
const payload = ${JSON.stringify(input)};
const setNativeValue = (el, value) => {
const proto = el instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
const setter = Object.getOwnPropertyDescriptor(proto, 'value')?.set;
setter?.call(el, value);
el.dispatchEvent(new Event('input', { bubbles: true }));
el.dispatchEvent(new Event('change', { bubbles: true }));
el.dispatchEvent(new Event('blur', { bubbles: true }));
};
const visible = (nodes) => nodes.find((node) => {
const style = window.getComputedStyle(node);
return style.visibility !== 'hidden' && style.display !== 'none' && node.offsetParent !== null;View on GitHub (pinned to 49907e53dc)
Solutions
- Check the flow state: if the expense was already submitted, skip creating a new one instead of retrying.
- Reload the create-expense page and re-run assertCreateExpenseSurface() before proceeding.
- If Mercury's UI wording changed, update the regexes (hasFinalSubmit/hasForm) in clis/mercury/utils.js to match the new copy.
- Wrap the call in try/catch for CommandExecutionError and treat it as 'already at review/confirmation stage'.
Example fix
// before
await assertCreateExpenseSurface(page);
await fillReimbursementFields(page, input);
// after
let surface;
try {
surface = await assertCreateExpenseSurface(page);
} catch (err) {
// already showing a submit/review surface — expense likely already created
return { skipped: true, reason: 'review-surface' };
}
await fillReimbursementFields(page, input); Defensive patterns
Strategy: try-catch
Validate before calling
const state = await assertCreateExpenseSurface(page);
if (state.hasFinalSubmit && !state.hasForm) {
// already at review/confirmation — do not proceed with fill/submit
return;
} Type guard
function isSafeExpenseSurface(p) {
return p != null && typeof p === 'object'
&& !(p.hasFinalSubmit === true && p.hasForm === false);
} Try / catch
try {
await assertCreateExpenseSurface(page);
await fillReimbursementFields(page, input);
} catch (err) {
if (err instanceof CommandExecutionError && /without a recognizable expense form/.test(err.message)) {
// likely already submitted; check expenses list via inspectMercury
return { skipped: true, reason: 'review-surface' };
}
throw err;
} Prevention
- Call assertCreateExpenseSurface immediately before filling/submitting, not minutes earlier.
- Treat this error as 'expense already submitted' rather than retrying the flow.
- After any redirect or timeout, reload the create-expense page and re-assert the surface.
- If Mercury's UI copy changes, update the hasForm/hasFinalSubmit regexes.
When it happens
Trigger: page body text matches /Submit expense/i together with /Review|receipt|amount|merchant|category|notes/i (hasFinalSubmit=true) but does not match /receipt|amount|merchant|category|notes|expense date/i (hasForm=false), typically right after the expense was already submitted or on a confirmation/review page.
Common situations: The expense was already submitted and the page shows only a confirmation with a Submit-style control; Mercury UI copy changed so form keywords no longer appear; automation resumed on a stale/redirected page.
Related errors
- Mercury is already showing a submit/review surface; refusing
- Mercury appears to have an existing expense review open; ref
- Could not find the Mercury Submit expense or New expense but
- Mercury reimbursement-draft requires Browser Bridge uploadFi
- Mercury receipt upload did not confirm exactly one uploaded
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/0bffa4159102aa8f.
Report an issue: GitHub.