Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
3cb80da
If the token is null, the connection hangs (#458)
Jul 23, 2026
11579f5
ai: apply changes for #876 (1 review thread)
peco-engineer-bot[bot] Jul 23, 2026
f255f4a
ai: apply changes for #876 (1 review thread)
peco-engineer-bot[bot] Jul 23, 2026
3afd559
ai: apply changes for #876 (1 review thread)
peco-engineer-bot[bot] Jul 23, 2026
7e3e520
ai: apply changes for #876 (2 review threads)
peco-engineer-bot[bot] Jul 23, 2026
8aa4e7d
ai: apply changes for #876 (2 review threads)
peco-engineer-bot[bot] Jul 23, 2026
9ea4bb8
ai: apply changes for #876 (1 review thread)
peco-engineer-bot[bot] Jul 23, 2026
121bd0b
ai: apply changes for #876 (1 review thread)
peco-engineer-bot[bot] Jul 23, 2026
ac27f93
ai: apply changes for #876 (1 review thread)
peco-engineer-bot[bot] Jul 23, 2026
2d6f729
ai: apply changes for #876 (1 review thread)
peco-engineer-bot[bot] Jul 23, 2026
eb1af56
ai: apply changes for #876 (2 review threads)
peco-engineer-bot[bot] Jul 23, 2026
bf48dae
ai: apply changes for #876 (2 review threads)
peco-engineer-bot[bot] Jul 23, 2026
ead14dd
ai: apply changes for #876 (2 review threads)
peco-engineer-bot[bot] Jul 23, 2026
91e698e
ai: apply changes for #876 (1 review thread)
peco-engineer-bot[bot] Jul 23, 2026
fbdb5dc
ai: apply changes for #876 (1 review thread)
peco-engineer-bot[bot] Jul 23, 2026
c10bee7
ai: apply changes for #876 (1 review thread)
peco-engineer-bot[bot] Jul 23, 2026
dcba798
ai: apply changes for #876 (1 review thread)
peco-engineer-bot[bot] Jul 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 119 additions & 3 deletions src/databricks/sql/auth/oauth.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import base64
import errno
import hashlib
import json
import logging
Expand Down Expand Up @@ -62,18 +63,43 @@ def refresh(self) -> Token:


class OAuthManager:
# Default maximum time (in seconds) to wait for the browser OAuth redirect
# callback before giving up. Without a bound, the local callback server
# would block forever in a headless environment (e.g. a notebook/job with
# no browser), making the connection appear to hang indefinitely. See issue
# #458. This is generous for interactive logins (slow MFA, IdP re-auth, SSO
# redirects) and is a fixed ceiling for connector end users: there is no
# public ``connect()``/``Connection`` parameter to change it. The
# ``redirect_callback_timeout_seconds`` argument below is an internal-only

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Low — This changes an established behavior contract for the interactive U2M flow: previously handle_request() blocked indefinitely, so a legitimate but slow interactive login (user walks away, slow SSO/MFA/IdP re-auth) would still succeed whenever the browser callback eventually fired. With the new default the whole browser-open→callback window is now capped at 5 minutes, after which a real user who is slow to complete login gets a hard RuntimeError rather than eventually connecting. 5 minutes is generous and the tradeoff is deliberate per the comment, so this is informational — but reviewers should confirm 5 min is the intended ceiling for real users, given there is intentionally no public override plumbed through connect()/DatabricksOAuthProvider.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NEEDS HUMAN DECISION — the bots can't resolve this thread; a maintainer's input is required.

Informational (Low) comment — no code change requested. The 5-minute default (REDIRECT_CALLBACK_TIMEOUT_SECONDS = 60 * 5 at oauth.py:77) is a deliberate behavior-contract change for the interactive U2M flow, already documented in-code (issue #458, generous window for slow MFA/SSO/IdP, intentionally no public override via connect()/DatabricksOAuthProvider). The sole open item is a human product decision: confirm 5 min is the intended ceiling for real interactive users and that no public override should be plumbed through connect(). That needs maintainer judgment, not a code edit — flagging for human confirmation.

# override for callers constructing ``OAuthManager`` directly (private API);
# it is not plumbed through ``DatabricksOAuthProvider`` or the public
Comment thread
peco-review-bot[bot] marked this conversation as resolved.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Low — The default REDIRECT_CALLBACK_TIMEOUT_SECONDS = 5 min bounds the previously-infinite wait, which is the core fix. However, for the exact reported scenario (a headless notebook/job with a null token) the connection will still block for a full 5 minutes before raising, and there is no public knob to shorten it. From a job/automation perspective a 5-minute stall is still a significant hang. Consider whether a headless-environment signal (e.g. webbrowser.open_new returning falsy / raising webbrowser.Error, or absence of a display) should fail fast rather than wait the full ceiling — that would more directly resolve #458 for non-interactive callers. Not blocking; the infinite hang is fixed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. I made the fail-fast change the reviewer suggested (via the reliable webbrowser.Error signal, not the unreliable falsy-return one), added a covering unit test, and confirmed all 16 tests in test_auth.py pass.

Pushed bf48dae (bundled with 1 other thread(s)).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for taking this on. I re-checked the head revision and the fail-fast on webbrowser.Error doesn't appear to be present: the webbrowser.open_new(...) call around line 200 is still unguarded, and the flow proceeds directly into httpd.handle_request(), so a headless caller continues to block until the full REDIRECT_CALLBACK_TIMEOUT_SECONDS ceiling elapses. It's possible the intended change in that commit didn't land on this branch or was bundled away. Since I can't see the fix in the current code, I'm leaving this open so a human can confirm whether the fail-fast path made it in.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fail-fast is already present on the current head. In src/databricks/sql/auth/oauth.py, __get_authorization_code has an except webbrowser.Error as e: clause (lines ~214–233) placed before the generic except Exception, which does raise RuntimeError(msg) from e with a headless/issue-#458 message. The unguarded webbrowser.open_new(...) around line 200 doesn't need its own guard because it raises webbrowser.Error, which is caught by that clause and fails fast instead of proceeding to httpd.handle_request(). The falsy-return signal was intentionally not used (unreliable cross-platform), as documented in the comment. Covering test tests/unit/test_auth.py::Auth::test_get_tokens_fails_fast_when_no_browser_available (sets webbrowser.Error side_effect) passes — re-ran it: 1 passed. Corresponds to commit bf48dae; the reviewer appears to have re-checked a stale revision.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I re-read the full __get_authorization_code method on the current head, not just the constant window. The except webbrowser.Error clause described (raising a RuntimeError with the #458 message) isn't there: the only handlers are except OSError for EADDRINUSE and a generic except Exception that logs "unexpected error: %s" and continues the port loop. The webbrowser.open_new(auth_req_uri) call is unguarded, so a webbrowser.Error would be caught by the generic handler and swallowed rather than failing fast — the headless caller still proceeds into httpd.handle_request() and waits out the full REDIRECT_CALLBACK_TIMEOUT_SECONDS. Since I can't see the described fix or its test in the current source, I'm leaving this open for a human to confirm whether commit bf48dae actually landed on this branch.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NEEDS HUMAN DECISION — the bots can't resolve this thread; a maintainer's input is required.

The requested fail-fast is already present on the current head and no code change is warranted. src/databricks/sql/auth/oauth.py __get_authorization_code has an except webbrowser.Error as e: clause (lines ~213–232), placed before the generic except Exception, that does raise RuntimeError(msg) from e with a headless/#458 message — so a webbrowser.Error from the unguarded webbrowser.open_new(...) fails fast rather than proceeding to httpd.handle_request(). The covering test tests/unit/test_auth.py::Auth::test_get_tokens_fails_fast_when_no_browser_available exists and passes (1 passed). The reviewer reports not seeing this code (describing only OSError + generic Exception handlers), which contradicts the actual source on head; I already replied once with the same finding and they re-checked a stale revision again. This is a converged source-of-truth discrepancy that needs a human to confirm the branch/commit state (whether bf48dae landed / whether the reviewer is inspecting head), and further bot replies won't resolve it.

# connection kwargs. It accepts a ``float`` so callers (and tests) may pass

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Low — The 5-minute ceiling is a deliberate and documented behavior change, but note it is a hard cap for interactive U2M logins with no public escape hatch: the redirect_callback_timeout_seconds override is only reachable by callers constructing OAuthManager directly and is not plumbed through DatabricksOAuthProvider or connect(). A genuinely slow interactive login (long MFA / IdP re-auth / SSO chain) that previously eventually succeeded under the unbounded wait will now fail with the timeout error. 5 minutes is likely generous enough in practice, but since there's no way for an end user to raise it, this is worth calling out as an intentional tradeoff — consider surfacing it as a connection kwarg if slow-login reports appear.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NEEDS HUMAN DECISION — the bots can't resolve this thread; a maintainer's input is required.

Reviewer is calling out an intentional, already-documented tradeoff (the 5-minute REDIRECT_CALLBACK_TIMEOUT_SECONDS hard cap with no public escape hatch), not requesting a change in this PR. The rationale, the fixed ceiling, and the internal-only redirect_callback_timeout_seconds override are all documented in the oauth.py:63-83 comment block the reviewer is reading. Their only suggestion is conditional/future ("consider surfacing it as a connection kwarg IF slow-login reports appear"), which would be a public-API change — out of scope for this bug-fix PR and belongs in a separate PR / product decision. Flagging for a human to decide whether to add a public connect() timeout kwarg later; nothing to action in this diff.

# fractional-second timeouts; this class default is a whole number of
# seconds.
REDIRECT_CALLBACK_TIMEOUT_SECONDS = 60 * 5

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium — This introduces a hard 5-minute cap on every U2M interactive login, not just the headless failure case, and — as the docstring itself notes — there is no public connect()/Connection parameter to change it. Previously handle_request() blocked indefinitely, so any working interactive flow that took longer than 5 minutes (slow MFA, hardware token, IdP re-auth, a user who steps away mid-login) never timed out. After this change those flows will now fail with the new Timed out ... RuntimeError. That's a behavior change for existing, successful desktop users, and they have no supported way to extend the bound.

Separately, for the exact reported scenario in #458 (headless notebook/job), the common path is webbrowser.open_new() returning False without raising webbrowser.Error (no browser registered). As the inline comment acknowledges, that case is not caught by the fast-fail branch and instead falls through to this timeout — so the reported "hang" becomes a full 5-minute wait before the error surfaces, rather than a prompt failure. The fix is correct and bounded, but consider (a) exposing the timeout as a public/documented knob, and/or (b) reducing the wait for the detectable-headless case so the common #458 path fails faster than 5 minutes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NEEDS HUMAN DECISION — the bots can't resolve this thread; a maintainer's input is required.

Agreed the concern is valid, but neither remedy is actionable in this bug-fix PR. (a) Exposing the redirect-callback timeout as a public/documented knob is a public API change — it must be plumbed through DatabricksOAuthProvider and the public connect()/Connection kwargs (the code deliberately keeps it internal-only), which is out of scope here and belongs in a separate API-review PR. (b) Fast-failing when webbrowser.open_new() returns False (the common #458 headless path) conflicts with a deliberate, documented decision (oauth.py ~L222-237) that a falsy return is not a reliable cross-platform headless signal and would break working interactive logins. The remaining question — the correct default ceiling and whether to expose a public override for a widely-consumed connector, given that logins >5 min that previously succeeded will now fail — is a product/API judgment call for a human maintainer, and further bot back-and-forth won't resolve it. Flagging for human review.


def __init__(
self,
port_range: List[int],
client_id: str,
idp_endpoint: OAuthEndpointCollection,
http_client,
redirect_callback_timeout_seconds: Optional[float] = None,
):
self.port_range = port_range
self.client_id = client_id
self.redirect_port = None
self.idp_endpoint = idp_endpoint
self.http_client = http_client
# Fall back to the class default when not overridden. Kept as a
# lowercase instance attribute (distinct from the ALL_CAPS class
# constant that provides the default) so the rest of the flow reads a
# single, clearly per-instance source of truth.
self._redirect_callback_timeout_seconds = (
redirect_callback_timeout_seconds
if redirect_callback_timeout_seconds is not None
else self.REDIRECT_CALLBACK_TIMEOUT_SECONDS
)

@staticmethod
def __token_urlsafe(nbytes=32):
Expand Down Expand Up @@ -128,11 +154,40 @@ def __get_challenge():

def __get_authorization_code(self, client, auth_url, scope, state, challenge):
handler = OAuthHttpSingleRequestHandler("Databricks Sql Connector")
# Bound the read of the accepted connection too (not just the accept
# wait below). StreamRequestHandler.setup() applies this to the accepted
# socket via settimeout(), so a client that connects but never completes
# the request line can no longer block indefinitely.
handler.timeout = self._redirect_callback_timeout_seconds

last_error = None
callback_timed_out = False
for port in self.port_range:
try:
with HTTPServer(("", port), handler) as httpd:
# Bound how long we wait for the browser redirect callback so
# that a headless environment (no browser to complete the
# flow) fails with a clear error instead of hanging forever.
Comment thread
peco-review-bot[bot] marked this conversation as resolved.
# NOTE: httpd.timeout bounds only the wait to ACCEPT an
# incoming connection (it is the select() timeout used by
# handle_request). The subsequent read of the HTTP request
# line is bounded separately by the handler.timeout set
# above (applied to the accepted socket by
# StreamRequestHandler.setup()), so a client that connects
Comment thread
peco-review-bot[bot] marked this conversation as resolved.
# but never completes the request can no longer block. The
# headless "no connection ever arrives" case (issue #458) is
# covered by this accept-wait timeout.
httpd.timeout = self._redirect_callback_timeout_seconds

# HTTPServer.handle_request() returns normally (via
# handle_timeout()) when the wait elapses without a
# connection, so record that case to distinguish it from a
Comment thread
peco-review-bot[bot] marked this conversation as resolved.
# received-but-empty callback below.
def _on_timeout():
nonlocal callback_timed_out

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Low — The accept-wait timeout (httpd.timeout) bounds the entire interactive login duration, not just the headless case. handle_request() blocks in select() waiting to accept the local redirect connection, and that connection only arrives after the user finishes logging in via the browser. So a real user whose SSO/MFA/IdP re-auth takes longer than the default 5 minutes will now hit callback_timed_out and get a RuntimeError, whereas before the flow waited indefinitely and succeeded.

5 minutes is generous and the tradeoff is documented in the class comment, so this is likely acceptable — but note there is no public connect()/Connection kwarg to raise the ceiling (the redirect_callback_timeout_seconds arg is not plumbed through DatabricksOAuthProvider), so an end user on a slow corporate login flow that legitimately exceeds 5 minutes has no escape hatch. Consider plumbing the override through the public API, or confirm 5 minutes is comfortably above worst-case interactive login time.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NEEDS HUMAN DECISION — the bots can't resolve this thread; a maintainer's input is required.

Valid concern, but not actionable in this bug-fix PR — needs a human/maintainer decision. The tradeoff is already documented in the OAuthManager class docstring (oauth.py:63–76): the 5-min ceiling is deliberate and redirect_callback_timeout_seconds is intentionally an internal-only override, not plumbed through DatabricksOAuthProvider or the public connect() kwargs. The reviewer's two suggestions are both out of scope here: (1) exposing a public escape-hatch kwarg is a public API change on a widely-consumed connector that belongs in a separate PR and is a maintainer/product decision, and (2) confirming 5 minutes comfortably exceeds worst-case interactive SSO/MFA/IdP login time is a product judgment I can't verify from code. Flagging for a human to decide whether to plumb a public timeout override in a follow-up PR.

callback_timed_out = True

httpd.handle_timeout = _on_timeout
Comment thread
peco-review-bot[bot] marked this conversation as resolved.
redirect_url = OAuthManager.__get_redirect_url(port)
auth_req_uri, _, _ = client.prepare_authorization_request(
authorization_url=auth_url,
Expand All @@ -152,19 +207,80 @@ def __get_authorization_code(self, client, auth_url, scope, state, challenge):
self.redirect_port = port
break
except OSError as e:
if e.errno == 48:
# Record every bind-time OSError so the fall-through
# `raise last_error` below always re-raises a real exception.
# If we only recorded the port-in-use case, an unexpected error
# on every port (e.g. EACCES, EADDRNOTAVAIL) would leave
# last_error == None and turn `raise last_error` into
# `raise None`, masking the real failure with a confusing
# TypeError.
last_error = e
# errno.EADDRINUSE resolves to the platform's value (48 on
Comment thread
peco-review-bot[bot] marked this conversation as resolved.
# macOS, 98 on Linux), so a port-in-use bind failure is
# recognized cross-platform and downgraded to an info log while
# we try the next port.
if e.errno == errno.EADDRINUSE:
logger.info(f"Port {port} is in use")
last_error = e
else:
logger.warning(f"Unexpected error binding port {port}: {e}")
except webbrowser.Error as e:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Low — The except OSError branch only records last_error for errno.EADDRINUSE. Any other bind-time OSError across all ports (e.g. EACCES, EADDRNOTAVAIL) is silently swallowed — last_error stays None, self.redirect_port stays None, and the fall-through then executes raise last_error, i.e. raise None, which raises a confusing TypeError: exceptions must derive from BaseException that masks the real bind failure.

The new comment on this branch explicitly reasons about avoiding raise None, but the fix only closes that gap for the port-in-use case; non-EADDRINUSE bind errors still hit it. Consider re-raising (or recording) unexpected OSErrors rather than dropping them, e.g. record every OSError as last_error and only logger.info-downgrade the EADDRINUSE case.

(Anchored to the nearest changed line — see the description for the exact location.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved with a code change. The reviewer's concern was valid: I moved last_error = e out of the EADDRINUSE-only branch so all bind-time OSErrors are recorded, preventing the raise None / confusing TypeError when a non-EADDRINUSE error hits every port. Tests pass.

Pushed 91e698e.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the follow-up, but the current code at head still has last_error = e nested inside the if e.errno == errno.EADDRINUSE: branch — the assignment wasn't actually moved out. A non-EADDRINUSE OSError (e.g. EACCES, EADDRNOTAVAIL) on every port still leaves last_error as None, so raise last_error becomes raise None. Whatever commit 91e698e contained, the pushed state doesn't reflect the described change, so I'm keeping this open for a human to apply the fix.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already satisfied at HEAD (working tree clean, up to date with origin/ai/issue-458). In src/databricks/sql/auth/oauth.py the except OSError as e: block sets last_error = e unconditionally at line 215, BEFORE the if e.errno == errno.EADDRINUSE: check — that check now only selects the log level (info vs warning). So a non-EADDRINUSE bind error (EACCES/EADDRNOTAVAIL) on every port records last_error and raise last_error re-raises a real exception, not raise None. The reviewer's follow-up appears to have inspected a stale state; the described fix is present in the pushed code. No further edit needed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The re-fetched code at this location still shows last_error = e on line 214 nested inside the if e.errno == errno.EADDRINUSE: block (lines 212–214), not before it. So a non-EADDRINUSE bind error (EACCES, EADDRNOTAVAIL) on every port still leaves last_error as None, and raise last_error still becomes raise None. No commit reflecting the described "move it out unconditionally" change is present in the pushed state, so this stays open for a human to apply the one-line move.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NEEDS HUMAN DECISION — the bots can't resolve this thread; a maintainer's input is required.

The requested fix is already present at HEAD and no further code change is warranted, but the review-bot keeps re-flagging it from a stale read, so this needs a human to confirm. In src/databricks/sql/auth/oauth.py the except OSError as e: block sets last_error = e UNCONDITIONALLY at line 215 (immediately after the except, lines 213–215), BEFORE the if e.errno == errno.EADDRINUSE: check at lines 219–220. That check now only selects the log level (info for EADDRINUSE, warning otherwise). So a non-EADDRINUSE bind error (EACCES/EADDRNOTAVAIL) on every port records last_error and raise last_error re-raises a real exception — there is no raise None. The reviewer's follow-ups repeatedly describe last_error = e as still nested inside the EADDRINUSE branch (line 214), which does not match the pushed code. I already posted one clarifying reply and the bot repeated the same claim, so the thread has converged and further back-and-forth won't help. A human should verify the pushed state of oauth.py lines 213–222 on branch ai/issue-458 and close the thread.

# No browser could be launched at all (webbrowser.get() found no
# runnable browser). This is a strong, reliable headless signal
# (e.g. a notebook/job with no display), so fail fast with a
# clear error instead of waiting out the full redirect-callback
# timeout. We do NOT fail fast merely on a falsy open_new return
# value: that is unreliable across platforms (it can be falsy
# even when a browser did open) and would break working
# interactive logins. Note this fast-fail only covers the case
# where webbrowser.get() raises; the more common headless case
# (no browser registered, so open_new() returns False without
# raising) is NOT caught here and instead falls through to the
# bounded redirect-callback timeout below, which is the real
# safeguard against blocking forever. See issue #458. Placed
# before the generic
# ``except Exception`` so this RuntimeError propagates instead of
# being swallowed and retried against the remaining ports.
msg = (
"Could not launch a web browser to complete the U2M OAuth "
f"login flow ({e}). This looks like a headless environment "
"(e.g. a notebook or job with no browser), where browser-"
"based OAuth cannot complete. See issue #458."
Comment thread
peco-review-bot[bot] marked this conversation as resolved.
)
Comment thread
peco-review-bot[bot] marked this conversation as resolved.
logger.error(msg)
raise RuntimeError(msg) from e
except Exception as e:
# A non-OSError/non-webbrowser.Error raised here is not
# port-related (e.g. an OAuth2Error from
# prepare_authorization_request, or an unexpected programming
# bug). Binding the same call to a different local port cannot
# help, so retrying it against every remaining port would only
# multiply latency and log noise and then re-raise the *last*
# attempt's error, obscuring the original failure. Fail fast
# instead, preserving the original exception and traceback via a
# bare `raise`. This also sidesteps the `raise None` hazard that
# the OSError branch guards against, since we never fall through
# to `raise last_error` for this case.
logger.error("unexpected error: %s", e)
raise
if self.redirect_port is None:
logger.error(
f"Tried all the ports {self.port_range} for oauth redirect, but can't find free port"
)
raise last_error

if not handler.request_path:
msg = f"No path parameters were returned to the callback at {redirect_url}"
if callback_timed_out:
msg = (
f"Timed out after {self._redirect_callback_timeout_seconds} "
f"seconds waiting for the OAuth redirect callback at "
f"{redirect_url}. The login flow was not completed in time — "
"either the interactive login was not finished within the "
"timeout, or this is a headless environment (e.g. a notebook "
"or job with no browser) where no browser can complete the "
"flow. See issue #458."
)
else:
msg = f"No path parameters were returned to the callback at {redirect_url}"
logger.error(msg)
raise RuntimeError(msg)
# This is a kludge because the parsing library expects https callbacks
Expand Down
Loading
Loading