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:
pi@mbp-m1-2020
2026-10-12 07:57:15 +02:00
parent 0ce96fdb8b
commit 4e48456591
4 changed files with 170 additions and 86 deletions
+28 -15
View File
@@ -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 |