Skip to content

Commit 34b844f

Browse files
Merge pull request #259 from browser-use/fix/claude-login-await-success
Confirm OAuth success on the agent before reporting login exit
2 parents 0d50919 + 7e5430d commit 34b844f

1 file changed

Lines changed: 79 additions & 3 deletions

File tree

agent/box_agent.py

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1642,6 +1642,19 @@ async def _claude_login_read_loop(self, pid: int, fd: int) -> None:
16421642
)
16431643
except OSError:
16441644
LOG.info('claude_login: pty closed (pid=%s)', pid)
1645+
# The pty closed: claude either wrote ~/.claude.json and
1646+
# exited cleanly, or bailed without writing credentials.
1647+
# The general auth-poll loop runs on a 15s cadence once
1648+
# the box is past its first minute — too coarse to
1649+
# confirm success inside the cloud's exit watchdog, which
1650+
# is what makes a fast-successful sign-in surface a
1651+
# spurious "Try again". Check directly here at a tight
1652+
# cadence: on success emit `claude_authed` and skip the
1653+
# exited event; on timeout emit `claude_login_exited` so
1654+
# the cloud fails fast. Exactly one terminal event per
1655+
# attempt — no cloud-side race left to lose.
1656+
if await self._claude_login_await_success():
1657+
return
16451658
await self._send({'type': 'claude_login_exited'})
16461659
return
16471660
if data is None:
@@ -1673,6 +1686,36 @@ async def _claude_login_read_loop(self, pid: int, fd: int) -> None:
16731686
except Exception:
16741687
LOG.exception('claude_login: read loop crashed')
16751688

1689+
async def _claude_login_await_success(self, deadline_s: float = 15.0) -> bool:
1690+
"""After the login pty closes, watch for credentials to land.
1691+
1692+
`claude auth login` writes ~/.claude.json a beat after the user
1693+
pastes a valid code, then exits. The general `_auth_poll_loop`
1694+
runs on a 15s cadence once the box is past its first minute —
1695+
too coarse to confirm success inside the cloud's exit watchdog.
1696+
Poll `claude auth status` directly here every 0.5s for up to
1697+
`deadline_s`.
1698+
1699+
Returns True (and emits `claude_authed`) the moment creds appear;
1700+
False on timeout, leaving the caller to emit `claude_login_exited`.
1701+
Keeps `self._authed` in sync so `_auth_poll_loop` doesn't re-emit
1702+
a duplicate `claude_authed` on its next tick.
1703+
"""
1704+
loop = asyncio.get_running_loop()
1705+
deadline = loop.time() + deadline_s
1706+
while loop.time() < deadline:
1707+
if await check_claude_authed():
1708+
self._authed = True
1709+
try:
1710+
await self._send({'type': 'claude_authed'})
1711+
LOG.info('claude_login: success confirmed on pty-exit path')
1712+
except Exception:
1713+
LOG.exception('claude_login: claude_authed emit failed')
1714+
return True
1715+
await asyncio.sleep(0.5)
1716+
LOG.info('claude_login: no credentials within %.0fs of pty exit', deadline_s)
1717+
return False
1718+
16761719
async def _claude_login_code(self, *, code: str) -> None:
16771720
"""Pump the OAuth callback code into the pty's stdin."""
16781721
fd = self._claude_login_fd
@@ -1910,10 +1953,15 @@ async def _codex_login_read_loop(self, pid: int, fd: int) -> None:
19101953
)
19111954
except OSError:
19121955
LOG.info('codex_login: pty closed (pid=%s)', pid)
1956+
# Mirror of the claude_login pty-exit path: confirm
1957+
# success directly here at a tight cadence rather than
1958+
# leaving the cloud to race a 15s auth poll. On success
1959+
# emit `codex_authed` and skip the exited event; on
1960+
# timeout emit `codex_login_exited` so the cloud fails
1961+
# fast. Exactly one terminal event per attempt.
1962+
if await self._codex_login_await_success():
1963+
return
19131964
await self._send({'type': 'codex_login_exited'})
1914-
# Wake the auth poll so codex_authed flips quickly
1915-
# rather than waiting for the next 1s tick.
1916-
self._codex_auth_wakeup.set()
19171965
return
19181966
if data is None:
19191967
if self._codex_login_pid != pid:
@@ -1987,6 +2035,34 @@ async def _codex_login_read_loop(self, pid: int, fd: int) -> None:
19872035
except Exception:
19882036
pass
19892037

2038+
async def _codex_login_await_success(self, deadline_s: float = 15.0) -> bool:
2039+
"""After the codex device-auth pty closes, watch for the token.
2040+
2041+
Mirror of `_claude_login_await_success`. `codex login --device-auth`
2042+
writes its credentials and exits 0 once OpenAI signals the device
2043+
approved. Poll `codex login status` directly here every 0.5s for
2044+
up to `deadline_s` so success is confirmed without racing the
2045+
general `_codex_auth_poll_loop` cadence.
2046+
2047+
Returns True (and emits `codex_authed`) on success; False on
2048+
timeout. Keeps `self._codex_authed` in sync so the poll loop
2049+
doesn't re-emit a duplicate.
2050+
"""
2051+
loop = asyncio.get_running_loop()
2052+
deadline = loop.time() + deadline_s
2053+
while loop.time() < deadline:
2054+
if await check_codex_authed():
2055+
self._codex_authed = True
2056+
try:
2057+
await self._send({'type': 'codex_authed'})
2058+
LOG.info('codex_login: success confirmed on pty-exit path')
2059+
except Exception:
2060+
LOG.exception('codex_login: codex_authed emit failed')
2061+
return True
2062+
await asyncio.sleep(0.5)
2063+
LOG.info('codex_login: no credentials within %.0fs of pty exit', deadline_s)
2064+
return False
2065+
19902066
async def _codex_login_cancel(self) -> None:
19912067
await self._codex_login_cleanup()
19922068
await self._send({'type': 'ack', 'cmd': 'codex_login_cancel', 'ok': True})

0 commit comments

Comments
 (0)