Skip to content

fix: distinguish an unreachable backend from expired authentication - #300

Closed
practical-tools-lab wants to merge 2 commits into
jacob-bd:mainfrom
practical-tools-lab:fix/distinguish-unreachable-backend-from-expired-auth
Closed

fix: distinguish an unreachable backend from expired authentication#300
practical-tools-lab wants to merge 2 commits into
jacob-bd:mainfrom
practical-tools-lab:fix/distinguish-unreachable-backend-from-expired-auth

Conversation

@practical-tools-lab

Copy link
Copy Markdown

Problem

When the NotebookLM homepage fetch cannot complete, the auth recovery ladder in base.py falls through to the same final raise as a genuinely rejected cookie. A transport failure is therefore surfaced as:

Authentication expired. Run 'nlm login' in your terminal to re-authenticate.

That message is wrong in this case and actively harmful:

  1. It tells the user to re-authenticate, which cannot fix a network failure.
  2. Automated monitors that key on this error raise false credential alerts and escalate to a human.

The cause is that layer 1 discards why it failed:

except ValueError:
    # CSRF refresh failed (cookies expired) - continue to layer 2
    pass

The comment assumes expiry, but _refresh_auth_tokens raises ValueError for any refresh failure, including one where the request never reached a verdict.

Observed

Same profile, same cookies, within the same minute:

Command Result
nlm login --check x3 non-zero exit, network_error: ClientAuthenticationError
nlm notebook list x10 10/10 success

login --check even prints "your saved credentials may still be valid" while still exiting non-zero, so callers that check the exit code conclude expiry.

Change

  • Preserve the layer-1 failure cause instead of discarding it.
  • Add _is_unreachable_failure() to classify transport failures (httpx.TransportError, httpx.TimeoutException, OSError, and message markers) versus genuine rejection.
  • Raise a new TransientBackendError when the evidence only shows an unreachable backend.
  • Genuine expiry (an accounts.google.com redirect or an explicit expiry message) still raises AuthenticationError with the original text, so existing handlers are unaffected.

TransientBackendError is intentionally not a subclass of the auth error, so except AuthenticationError callers no longer swallow a network problem as expiry.

Verification

Classifier behavior, run under the installed interpreter:

Input Classified unreachable
ValueError("Could not reach NotebookLM (network_error)") True
ValueError("Authentication expired. accounts.google.com") False
httpx.ConnectError True
OSError("dns fail") True
None False

5/5 as expected. Both touched modules compile cleanly.

Scope

Two files, +55/-5. No behavior change for a real expiry path.

@jacob-bd

Copy link
Copy Markdown
Owner

Superseded by #301, which implements the same behavior with regression tests and passed all GitHub checks. Thank you @practical-tools-lab for identifying this issue and proposing the original direction.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants