fix(console): retry a late profile-mismatch 409 once when focus returned mid-request

This commit is contained in:
carlotestor committed 2026-09-24 17:09:08 +00:00
1 parent 6ebd12c251
commit 075f95aaf2
3 files changed
+78 -4

No files matched your search

@@ -124,6 +124,7 @@ var _clarifyEventSource = null, _clarifyFallbackTimer = null, _clarifyHealthTime
var _clarifyFallbackPollInFlight = false, _clarifyPollingSessionId = null, _clarifyMissingEndpointWarned = false;
var _approvalPendingBySession = new Map();
var _approvalProfilePausedSessionId = null, _clarifyProfilePausedSessionId = null;
var _promptPollerFocusEpoch = 0;
function _approvalPromptGeneration(){ return 0; }
function _clarifyPromptGeneration(){ return 0; }
function _approvalPollingSessionMissingOrMismatched(sid){ return !sid || !S.session || S.session.session_id !== sid; }
@@ -335,3 +336,68 @@ process.stdout.write(String(started));
out = subprocess.run([node, "-e", script], capture_output=True, text=True, timeout=30)
assert out.returncode == 0, out.stderr
assert out.stdout == "0"
def _run_late_409_after_focus(kind, cookie_back):
"""The request starts under the old profile, focus returns while it is in flight,
then the old-profile 409 lands. The poller must retry once under the current
cookie instead of pausing for good; it pauses only if the retry also mismatches."""
node = shutil.which("node")
if not node:
pytest.skip("node not installed")
approval = kind == "approval"
fns = [
_extract_fn(SESSIONS_JS, "_sessionProfileMismatchFromError"),
_extract_fn(MESSAGES_JS, "startApprovalPolling"),
_extract_fn(MESSAGES_JS, "_startApprovalFallbackPoll"),
_extract_fn(MESSAGES_JS, "stopApprovalPollingForSession"),
_extract_fn(MESSAGES_JS, "stopApprovalPolling"),
_extract_fn(MESSAGES_JS, "startClarifyPolling"),
_extract_fn(MESSAGES_JS, "_startClarifyFallbackPoll"),
_extract_fn(MESSAGES_JS, "stopClarifyPollingForSession"),
_extract_fn(MESSAGES_JS, "stopClarifyPolling"),
_extract_fn(MESSAGES_JS, "_resumeProfilePausedPromptPollers"),
]
harness = _HARNESS.replace("STATUS", "409").replace("BODY", json.dumps(_MISMATCH))
harness = harness.replace("async function api(){", """var mismatch = true, releaseFirst = null;
async function api(){
if (!releaseFirst) { await new Promise(r => { releaseFirst = r; }); return _mismatchThrow(); }
if (!mismatch) { calls.api++; return {pending: {id: 'p1'}}; }
return _mismatchThrow();
}
function _mismatchThrow(){""")
harness = harness.replace("function showApprovalForSession(){}", "function showApprovalForSession(){ calls.shown = (calls.shown||0) + 1; }")
harness = harness.replace("function showClarifyForSession(){}", "function showClarifyForSession(){ calls.shown = (calls.shown||0) + 1; }")
start = "startApprovalPolling" if approval else "startClarifyPolling"
timer = "_approvalPollTimer" if approval else "_clarifyFallbackTimer"
script = harness + "\n".join(fns) + f"""
const tick = () => new Promise(r => setTimeout(r, 0));
(async () => {{
{start}('sid1');
await tick(); // first request is in flight under the old cookie
mismatch = {'false' if cookie_back else 'true'}; // other tab switches the cookie (back or not)
_resumeProfilePausedPromptPollers(); // focus returns: nothing paused yet, so nothing to restart
releaseFirst(); // the late old-profile 409 lands
for (let i = 0; i < 5; i++) await tick();
process.stdout.write(JSON.stringify({{api: calls.api, shown: calls.shown || 0,
running: {timer} !== null}}));
}})();
"""
out = subprocess.run([node, "-e", script], capture_output=True, text=True, timeout=30)
assert out.returncode == 0, out.stderr
return json.loads(out.stdout)
@pytest.mark.parametrize("kind", ["approval", "clarify"])
def test_late_409_after_focus_retries_once_and_shows_pending(kind):
r = _run_late_409_after_focus(kind, cookie_back=True)
assert r["api"] == 2, "a 409 for a request that predates focus must be retried once"
assert r["shown"] == 1, "the pending prompt must be shown after the retry"
assert r["running"] is True, "polling must keep running after the successful retry"
@pytest.mark.parametrize("kind", ["approval", "clarify"])
def test_late_409_after_focus_pauses_when_retry_still_mismatches(kind):
r = _run_late_409_after_focus(kind, cookie_back=False)
assert r["shown"] == 0
assert r["running"] is False, "a retry that still mismatches must pause, not loop"
@@ -24,6 +24,7 @@ const S = {busy:true,session:{session_id:'s'}};
const _approvalPromptBelongsToActiveSession=()=>false;
let _approvalSessionId = 's', _approvalCurrentId = 'id';
let _approvalFallbackPollInFlight=false, _approvalPollTimer;
let _promptPollerFocusEpoch=0;
let _clarifyPollingSessionId, _clarifyPollTimer, _clarifyFallbackPollInFlight=false;
const _promptActiveSessionId=()=> 's';
const _approvalPollingSessionMissingOrMismatched=()=>false;