fix(web): clear the step-up retry on redirect handoff; justify the voids

Copilot round 4:

- the EMA `interactive` arm kept `pendingStepUpRetryRef`, on a comment
  claiming it was "still on its way to a redirect". That is wrong twice:
  `prepareOAuthRedirect` returns before the navigation is resolved, so a
  pre-navigation failure leaves the operation for a later, unrelated
  step-up to re-run; and the full-page navigation destroys the closure on
  the success path anyway. It now clears the ref, matching the standard
  step-up below.
- the two surviving `void (async () => …)()` sites now state why the
  caller cannot await, as AGENTS.md requires — the comments had described
  only what the catch handles.
- the `randomUUID` fallback test restored an own descriptor that never
  existed (it is inherited from `Crypto.prototype`), leaving an own
  `randomUUID: undefined` that would push every later test in the file
  onto the fallback path. It deletes the temporary property instead, and
  asserts the global is a function again afterwards.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Quk1hBUtyhkXowwMXzAnAY
Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-08-29 02:48:28 -04:00
commit d5ddaf8818

View file

@ -934,6 +934,8 @@ export function useOAuthRecovery({
if (!serverId) {
return;
}
// `void`: an event listener cannot await, and the `.catch` below is
// what terminates the chain.
void (async () => {
const { challenge, authorizationUrl } = event.detail;
const server = sessionRef.current.servers.find(
@ -1338,6 +1340,10 @@ export function useOAuthRecovery({
});
}
})().catch((err: unknown) => {
// `void`: an effect body cannot await, and this `.catch` is what
// terminates the chain — the justification AGENTS.md asks of every
// surviving `void`.
//
// Everything inside has its own arm; this catches what sits *between*
// them (#2165) — `setupClientForServer` throwing while it builds the
// client, and the post-resume `checkAuthChallengeSatisfied`. Both land
@ -1507,6 +1513,14 @@ export function useOAuthRecovery({
authChallenge: outcome.challenge,
recoverySource: stepUp.source,
});
// Handing off, so the retry goes too — as the standard (non-EMA)
// step-up below already does. A full-page redirect cannot carry a
// closure across the navigation, so keeping it buys nothing on the
// success path; and `prepareOAuthRedirect` returns before the
// navigation is resolved, so on the failure path it would survive
// as a stale operation a later, unrelated step-up could re-run
// (#2165).
pendingStepUpRetryRef.current = null;
return;
}
if (outcome.kind === "failed") {
@ -1514,8 +1528,7 @@ export function useOAuthRecovery({
// dismissed, so nothing will ever consume this operation — and a
// *later* step-up does not overwrite the ref, so leaving it would
// let that unrelated authorization silently re-run this command
// (#2165). The `interactive` arm above deliberately keeps it: that
// one is still on its way to a redirect.
// (#2165).
pendingStepUpRetryRef.current = null;
const failureMessage = emaStepUpFailureMessage(outcome.error.message);
notifications.show({