Skip to content

fix: recover from an expired provider session without a manual reload - #200

Open
Lukelumia wants to merge 1 commit into
sir-Unknown:mainfrom
Lukelumia:fix/coordinator-session-recovery
Open

Lukelumia wants to merge 1 commit into
sir-Unknown:mainfrom
Lukelumia:fix/coordinator-session-recovery

Conversation

@Lukelumia

@Lukelumia Lukelumia commented Sep 10, 2026

Copy link
Copy Markdown

Description

Problem

The provider session is established once, in async_setup_entry, and is never renewed while the entry keeps running. pycityvisitorparking does retry a request once after re-authenticating, but only when the provider answers with an explicit auth status. A portal that reports an expired session as a generic server error instead never reaches that path, so the dead session stays in place and every following update fails until the user reloads the entry by hand.

Relates to #198. That issue and sir-Unknown/pyCityVisitorParking#93 address the DVSPortal-specific status mapping; this PR addresses the integration-side half — the coordinator's inability to recover from any stale session, whatever the provider returns.

Confirmed on Leiden (/DVSWebAPI/api), which answers POST /login/getbase with 500 and application/json {"Message":"An error has occurred."} — a shape #93 deliberately leaves as ProviderError. From my instance: status=500 operation=fetch_all logged 8227 times since June, coordinator data frozen for three weeks, config entry still reporting loaded throughout. Details in my comment on #198.

Change

  • auth.py (new) — the login moves out of async_setup_entry, so setup and runtime recovery establish a session identically, including persisting and clearing resolved_login_params.
  • Coordinator — re-authenticate once and retry the fetch when an update fails with AuthError or ProviderError. Errors a new session cannot fix are re-raised untouched: a NetworkError never reached the provider, and RateLimitError / ServiceUnavailableError would only be made worse by adding a login. Attempts are spaced by RELOGIN_COOLDOWN (15 min) so a provider outage cannot turn every poll into a login.
  • A re-login that is itself rejected still raises ConfigEntryAuthFailed, so genuinely wrong credentials keep triggering the reauth flow (AGENTS.md §11, §13).
  • Entity availabilityavailable now follows coordinator.last_update_success. These entities extend BaseCoordinatorEntity, which unlike CoordinatorEntity defines no available, so the sensors kept reporting their last successful values while every update failed. That is what made the failure silent: no unavailable entities, no reauth notification, just values frozen at the moment the session expired.

No provider-specific branching is introduced, per AGENTS.md §7.

Tests

Eight cases added:

  • stale-session ProviderError recovers via one re-login and a retried fetch
  • AuthError likewise recovers
  • a rejected re-login still raises ConfigEntryAuthFailed
  • the cooldown allows one login per window, and another once it lapses
  • RateLimitError, NetworkError and a credential-less entry attempt no login at all
  • entity availability follows the coordinator

Five of the eight fail on the parent commit. Full suite: 157 passed.

Not in scope

The service handlers (start_reservation etc.) call the provider directly and would still fail on a dead session until the next poll heals it. Happy to follow up if you want those routed through the same recovery.

Type of change

  • Bug fix
  • New feature
  • Refactor / cleanup
  • Documentation
  • Dependencies / maintenance

Checklist

  • ruff check and ruff format pass (also mypy: clean)
  • hassfest passes — not run locally, relying on CI
  • Tests added or updated
  • Frontend built locally (yarn build) if frontend changed — n/a, no frontend changes
  • CHANGELOG.md updated (for user-facing changes) — n/a, no CHANGELOG.md in this repo

🤖 Generated with Claude Code

The provider session is established once, in `async_setup_entry`, and is
never renewed while the entry keeps running. `pycityvisitorparking` does
retry a request once after re-authenticating, but only when the provider
answers with an explicit auth status. A portal that reports an expired
session as a generic server error instead (DVSPortal answers
`POST /login/getbase` with `500 {"Message":"An error has occurred."}`)
never reaches that path, so the dead session stays in place and every
following update fails until the user reloads the entry by hand.

Reloading only helps because it logs in again, so do that automatically:

- Move the login out of `async_setup_entry` into `auth.py`, so setup and
  runtime recovery establish a session the exact same way, including the
  persisting and clearing of provider-resolved login params.
- Let the coordinator re-authenticate once and retry the fetch when an
  update fails with an auth or provider error. Errors a new session
  cannot fix are re-raised untouched: a `NetworkError` never reached the
  provider, and rate-limit or maintenance responses would only be made
  worse by adding a login. Attempts are spaced by `RELOGIN_COOLDOWN` so a
  provider outage cannot turn every poll into a login.
- A re-login that is itself rejected still raises `ConfigEntryAuthFailed`,
  so genuinely wrong credentials keep triggering the reauth flow.

Also derive entity availability from the coordinator. These entities
extend `BaseCoordinatorEntity`, which — unlike `CoordinatorEntity` — has
no `available` property, so the sensors kept reporting their last
successful values while every update failed. That is what made the
failure silent: no unavailable entities, no reauth notification, just
values frozen at the moment the session expired.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017rNpb7SZNGUMtU71kkPcBb
@github-actions github-actions Bot added python Changes related to Python dependencies or tooling. bugfix Inconsistencies or issues which will cause a problem for users or implementers. labels Sep 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix Inconsistencies or issues which will cause a problem for users or implementers. python Changes related to Python dependencies or tooling.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant