jackwener/OpenCLI · error · CommandExecutionError
Mercury did not reach the Review step with the final Submit
Error message
Mercury did not reach the Review step with the final Submit expense button visible
What it means
After the Review click succeeds, the automation snapshots the Review step and requires both a Review indicator (hasReview) and the final 'Submit expense' button to be present. If either is missing, the flow did not actually reach the expected Review step, so it aborts rather than clicking an unknown button.
Source
Thrown at clis/mercury/reimbursement-draft.js:105
.filter((node) => {
const style = window.getComputedStyle(node);
return style.visibility !== 'hidden' && style.display !== 'none' && node.offsetParent !== null;
});
const el = candidates.find((node) => norm(node.innerText || node.textContent || '') === 'review');
if (!el) return { clicked: false };
el.click();
return { clicked: true, text: el.innerText || el.textContent || '' };
})()`);
if (!reviewClick || typeof reviewClick !== 'object' || typeof reviewClick.clicked !== 'boolean') {
throw new CommandExecutionError('Mercury returned malformed Review click result');
}
if (!reviewClick.clicked) {
throw new CommandExecutionError('Mercury Review button was not clicked; inspect the page for validation errors');
}
await page.wait({ time: 2 });
const review = await reviewSnapshot(page);
if (!review.hasReview || !review.hasSubmitExpenseButton) {
throw new CommandExecutionError('Mercury did not reach the Review step with the final Submit expense button visible');
}
if (input.closeAfterReview) {
await page.evaluate(`(() => {
const norm = (s) => String(s || '').replace(/\\s+/g, ' ').trim().toLowerCase();
const el = Array.from(document.querySelectorAll('button, a, [role="button"], [role="link"]'))
.find((node) => norm(node.innerText || node.textContent || '') === 'close');
el?.click();
return { clicked: Boolean(el) };
})()`);
await page.wait({ time: 1 });
}
return [{
status: 'review_ready',
url: review.url,
receipt: receiptBasename(input.receipt),
uploaded: true,View on GitHub (pinned to 49907e53dc)
Solutions
- Increase the wait after the Review click (e.g. page.wait({ time: 5 })) or poll with waitFor until hasReview && hasSubmitExpenseButton is true.
- Inspect the page state at failure — check for validation banners, session-expiry prompts, or interstitial modals.
- Re-authenticate if the Mercury session expired mid-flow.
- If Mercury changed markup, update reviewSnapshot's selectors for the Review step / 'Submit expense' button in reimbursement-draft.js.
Example fix
// before
await page.wait({ time: 2 });
const review = await reviewSnapshot(page);
if (!review.hasReview || !review.hasSubmitExpenseButton) {
throw new CommandExecutionError('Mercury did not reach the Review step with the final Submit expense button visible');
}
// after
let review = null;
for (let i = 0; i < 10; i++) {
await page.wait({ time: 1 });
review = await reviewSnapshot(page);
if (review.hasReview && review.hasSubmitExpenseButton) break;
}
if (!review || !review.hasReview || !review.hasSubmitExpenseButton) {
throw new CommandExecutionError('Mercury did not reach the Review step with the final Submit expense button visible');
} Defensive patterns
Strategy: retry
Validate before calling
// poll for the Review step instead of a fixed wait
let review = null;
for (let i = 0; i < 10 && !review; i++) {
await page.wait({ time: 1 });
const s = await reviewSnapshot(page);
if (s.hasReview && s.hasSubmitExpenseButton) review = s;
} Type guard
function reachedReviewStep(s) { return Boolean(s) && s.hasReview === true && s.hasSubmitExpenseButton === true; } Try / catch
try {
await draftReimbursement(input);
} catch (err) {
if (err.message.includes('did not reach the Review step')) {
await page.wait({ time: 5 });
const s = await reviewSnapshot(page);
if (!(s.hasReview && s.hasSubmitExpenseButton)) throw err;
// continue the flow manually
} else throw err;
} Prevention
- Replace fixed waits with polling/waitFor on the target element.
- Handle session-expiry and 2FA interstitials before the flow starts.
- Keep reviewSnapshot selectors in sync with Mercury's UI.
- Check for server-side error banners after each step.
When it happens
Trigger: reviewSnapshot(page) returns { hasReview: false } or { hasSubmitExpenseButton: false } — the click happened but the page did not transition to the Review step within the 2-second wait, or the step rendered without the final Submit expense button.
Common situations: Mercury is slow and 2 seconds is not enough for the Review step to render; a validation or server-side error appeared after clicking Review; Mercury UI change altered the 'Submit expense' button text/testid; a modal or interstitial (2FA, session expiry) intercepted navigation.
Related errors
- Could not switch to ${wantModel} model
- Unexpected 12306 probe: ${JSON.stringify(probe)}
- ChatGPT did not create a conversation URL after sending the
- ChatWise response
- No Claude response appeared within ${timeoutSeconds}s. Re-ru
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/34fe2bd773a9a57c.
Report an issue: GitHub.