fix(cli): don't render a non-expiring refresh token as expired - #425
Conversation
`pipefy auth status` computed the refresh-token expiry as `obtained_at + refresh_expires_in`. Keycloak sends `refresh_expires_in: 0` to advertise a non-expiring refresh token, but `_iso_expiry` reads `0` as a 0-second TTL and places expiry at the login instant — so the durable credential was always shown as `expired` (and `refresh_expires_at` was a past timestamp in `--json`). Eager refresh resets `obtained_at` to now on every rotation, so it stays perpetually "expired". Normalize the sentinel where the refresh token is read (`refresh_expires_in or None`), leaving `_iso_expiry` generic so a genuine 0-second access-token TTL still reads as expired. Purely a status-readout change; the refresh decision (`_is_stale`) never consults this value — it's only carried forward verbatim on rotation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Reviewed while this PR is still a draft. The status-readout fix looks correct and ready to land once you mark it ready for review (undraft): Keycloak refresh_expires_in: 0 no longer renders as an expired refresh token, _iso_expiry stays generic for access-token TTLs, and refresh/_is_stale is untouched. Local auth-status tests and CI on tip are green.
Please undraft when you consider it ready for review / merge. This APPROVE is on the current tip (9267fc1) as a signal that the change itself is good; it is not a request to merge while still draft.
What worked well
- Honest
unknown/nullinstead of claiming"never"(idle timeout can still invalidate refreshes). - Docs note for
refresh_expires_atupdated in the same PR. - Regression test fails if the
or Nonesentinel handling is reverted.
Review path
- Checked out the PR tip, ran CLI auth-status unit tests and ruff; CI lint+test green.
- Cross-checked sibling #409 (Windows
dpapikeychain): independent merge order, non-overlapping hunks, auto-merge clean. No findings to carry from that PR onto this tip.
Danielmoraisg
left a comment
There was a problem hiding this comment.
Thanks for this! It was driving me crazy that the agents were always talking to me about how long we had until the token expiration ❤️
|
we'll ship this later on in this week, I'd like to thoroughly test it beforehand. For now, thanks and good job! |
Summary
pipefy auth statusalways showedRefresh token: expired(andrefresh_expires_atas a past timestamp in--json), even on a freshly-authenticated session — misleading users, and agents polling auth during long-running work, into thinking re-login is imminent.Refresh-token expiry is computed as
obtained_at + refresh_expires_in. Keycloak sendsrefresh_expires_in: 0to advertise a non-expiring refresh token, but that0was read as a 0-second TTL, placing expiry at the login instant. Eager refresh resetsobtained_atto now on every rotation, so it sat perpetually at ~now → perpetually "expired".Fix: normalize the sentinel where the refresh token is read (
refresh_expires_in or None), so0→ no expiry →unknown(text) /null(JSON)._iso_expirystays generic, so a genuine 0-second access-token TTL still reads as "expired". Purely a status-readout change; the refresh decision (_is_stale) never consults this value — it's only carried forward verbatim on rotation.Deliberately not rendered as "never": per Keycloak's issue tracker,
refresh_expires_in: 0doesn't guarantee the token never expires — with "Offline Session Max Limited" off but a non-zero "Offline Session Idle", the server still enforces an idle timeout and rejects refreshes withinvalid_grant.unknown/nullis the honest readout.Confirmed against a live session:
refresh_expires_in = 0,expires_in = 300, recentobtained_at.Testing performed
uv run --package pipefy-cli pytest packages/cli/tests/→ 341 passed, 13 skippeduv run ruff check packages/cli/src/pipefy_cli/commands/auth.py→ All checks passed!test_status_non_expiring_refresh_token_not_reported_expired(seedsrefresh_expires_in=0; asserts--jsonrefresh_expires_at is Noneand the text line is notexpired).Docs
docs/cli/auth.md: updated therefresh_expires_atJSON-schema note —nullnow also covers an active stored session whose refresh token advertises no expiry (refresh_expires_in: 0).