aws-sso: read the session end from AWS's token cap; log in 30 s after it
Overnight 2026-10-12: in the session's last hour AWS issued tokens capped at the end (00:18:48 local, 8 h after sign-in); the old 'expiresAt advanced' rule read their millisecond jitter as 'refreshed'. A refusal at 00:13 led to a login that inherited the ending session, and the 30-min wait then delayed the useful login to 00:47. Now: new full token = refreshed; new token issued for < 55 min = capped, its expiry is the end (session_end_seen, status 'until HH:MM'), login at end+30 s; nothing written = refused, login at once. anchor logs token_min/inherited from the cap, not a clock. 2 min between unprompted logins after a success; 30 min, 1 h, 2 h, 4 h after unanswered ones.
This commit is contained in:
+28
-15
@@ -14,17 +14,25 @@ Reference (commands, variables, mechanism): the `aws-sso.ts` section of the
|
||||
|
||||
- The **1-hour access token** is renewed by the AWS SDK by itself. You never see that.
|
||||
- The **session** behind it is your browser's AWS sign-in. Its maximum length is set
|
||||
by your IdC admin, and it is not predictable from the client: we measured 8 h on
|
||||
one day and at most 6 h on the next. When it ends, AWS refuses further renewals.
|
||||
by your IdC admin (measured 8 h here). In its last hour AWS still issues new tokens
|
||||
but caps them at the session's end; after the end it refuses renewals.
|
||||
- A login while the browser sign-in is still valid completes silently but **inherits
|
||||
the same end**, so renewing early gains nothing. Only a login after the end starts
|
||||
a new session.
|
||||
- AWS sometimes refuses a renewal while the session still has time left (cause
|
||||
unknown): seen once by the keeper's check, 5 min before the end, and once as a failed
|
||||
request about 2 h before it. A login then worked at once.
|
||||
|
||||
So the keeper **detects** the end instead of guessing it. In the last 5 minutes of
|
||||
each 1-hour token it makes the renewal attempt itself, through the same SDK pi uses.
|
||||
If AWS refuses, it starts the login at once, while the old token still covers
|
||||
requests for a few minutes. If a request fails anyway (pi was closed, the laptop
|
||||
slept), it logs in and then sends `continue` so the interrupted run resumes.
|
||||
So the keeper **reads** the end instead of guessing it. In the last 5 minutes of each
|
||||
1-hour token it makes the renewal attempt itself, through the same SDK pi uses:
|
||||
|
||||
- **new full token:** the session goes on;
|
||||
- **new token cut short:** its expiry is the session's end, and the keeper logs in
|
||||
30 s after it (the cut-short token covers requests until then);
|
||||
- **refused:** it logs in at once, while the old token still covers requests.
|
||||
|
||||
If a request fails anyway (pi was closed, the laptop slept), it logs in and then sends
|
||||
`continue` so the interrupted run resumes.
|
||||
|
||||
The permanent fix is administrative. Your IdC admin can raise the session length
|
||||
(up to 90 days) in the IAM Identity Center console. With that, the keeper has almost
|
||||
@@ -98,8 +106,9 @@ PI_AWS_SSO_MODE=remote # only on headless/remote devices
|
||||
```text
|
||||
keeper: ON · local (default) · push: ntfy.sh (topic 3fb657b1) · auto-continue: on
|
||||
sso-session: MySession (profile chain Bedrock → Base)
|
||||
session: alive · started ≈ 22:19 · 1-h token until 23:19 (next check from 23:14)
|
||||
last check: 23:14 · refreshed
|
||||
session: alive since ≈ 00:47 · ends 08:47 (AWS caps tokens there) → login just after
|
||||
token: valid until 08:47 · next check from 08:42
|
||||
last check: 07:43 · capped: session ends 08:47
|
||||
last login: 2026-10-11 22:19:24 · ok (local)
|
||||
```
|
||||
|
||||
@@ -107,8 +116,10 @@ The topic is shown only as a fingerprint, never in clear.
|
||||
|
||||
## What happens when a session ends
|
||||
|
||||
1. **Detected:** in the 5 minutes before the hourly token runs out, AWS refuses the
|
||||
renewal. Log: `probe … result: refused`.
|
||||
1. **Detected:** in the session's last hour the hourly check gets a token cut short at
|
||||
the end (`probe … result: capped`, then `session_end_seen`), and the status line
|
||||
shows `AWS ✓ until 08:47`. The login starts 30 s after that time. If AWS refuses
|
||||
the renewal instead (`probe … result: refused`), the login starts at once.
|
||||
2. **Login starts:**
|
||||
- `local`: a tab opens on the host. If it has not finished after 60 s, you get
|
||||
the push "AWS SSO login waiting".
|
||||
@@ -119,7 +130,7 @@ The topic is shown only as a fingerprint, never in clear.
|
||||
noticed.
|
||||
4. **If nobody approves:** the old token runs out, the task's next request fails
|
||||
and the run pauses. The keeper tries again after 30 min, then 1 h, 2 h, and then
|
||||
every 4 h, so an unattended night produces only a few pushes. Each attempt brings
|
||||
every 4 h (between successful logins it waits only 2 min), so an unattended night produces only a few pushes. Each attempt brings
|
||||
a fresh code, and the keeper waits up to 10 minutes for each. When you approve,
|
||||
even hours later, the run resumes with `continue`.
|
||||
|
||||
@@ -136,8 +147,9 @@ The status line shows `AWS ✓ since 22:19`, `AWS ⏳ approve ABCD-EFGH`,
|
||||
|
||||
| Event | Meaning |
|
||||
|---|---|
|
||||
| `anchor` | A new refresh token was seen. `inherited: true` = silent renewal inside the same session; `false` = new session |
|
||||
| `probe` | Renewal attempt inside the 5-min window: `refreshed` (fine), `refused` (session over), `unavailable` (pi's AWS SDK could not be loaded; says nothing about the session) |
|
||||
| `anchor` | A new refresh token was seen (a login). `token_min` = its token's lifetime: 60 = a new session; less (`inherited: true`) = it joined the ending session |
|
||||
| `probe` | Renewal attempt inside the 5-min window: `refreshed` (new full token), `capped` (token cut at the session end, `ends`), `refused` (nothing issued: log in now), `unavailable` (pi's AWS SDK could not be loaded; says nothing about the session) |
|
||||
| `session_end_seen` | The session's end read from a capped token |
|
||||
| `login_trigger`, `login_start` | A login started, with the reason and mode |
|
||||
| `device_code_shown` | `remote`: the code shown to you |
|
||||
| `login_end` | `ok: true` = a new refresh token arrived; otherwise `detail` has the CLI's last line |
|
||||
@@ -174,7 +186,8 @@ The status line shows `AWS ✓ since 22:19`, `AWS ⏳ approve ABCD-EFGH`,
|
||||
|---|---|
|
||||
| `local` on a macOS host, browser still signed in: silent, ~3–7 s | verified |
|
||||
| `local` after the session ended: browser re-signed in through the IdP silently, run auto-continued | verified once (request-failed path) |
|
||||
| Detection by refresh attempt before any request fails | unit-tested; first live run pending |
|
||||
| Session end detected before any request failed; unattended silent login (00:13, 00:47) | verified overnight 2026-10-12, but the 00:47 login came 29 min after the end (fixed: now 30 s after) |
|
||||
| Login 30 s after a capped end | unit-tested; first live run pending |
|
||||
| ntfy push: delivery, title, priority, tap link, UTF-8 body | verified against ntfy.sh |
|
||||
| `remote` end to end (code → push → approve on phone) | not yet |
|
||||
| `local` on Linux/Windows hosts | not yet |
|
||||
|
||||
Reference in New Issue
Block a user