10 KiB
AWS SSO keeper (extensions/aws-sso.ts) — setup and operation
The keeper keeps pi's Bedrock access alive across AWS IAM Identity Center (IdC)
session ends, so a long or overnight run does not stop at "Token is expired". It
works like Claude Code's awsAuthRefresh: when the session ends it runs
aws sso login for you. It never approves anything itself. Either your browser
completes the login silently because it is still signed in, or you approve it, on
the machine or on your phone.
Reference (commands, variables, mechanism): the aws-sso.ts section of the
README. This page covers setup, daily use and troubleshooting.
Why sessions end, and why a timer cannot fix it
- 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 (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 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 nothing to do.
Pick a mode for each device
| Situation | Mode | What a renewal looks like |
|---|---|---|
| Mac you sit at; pi-devbox runs on it | local (default) |
A browser tab opens on the Mac and normally completes by itself ("Your credentials have been shared successfully…"). If it needs you (MFA), you get a push. |
| Linux/Windows desktop with a browser | local |
Same idea, but not verified yet: starting the desktop browser from an SSH session may need a host-specific PI_AWS_SSO_LOCAL_CMD. |
Headless server; you ssh in and run pi in tmux |
remote |
Push to your phone with a link and code. Tap it, check the code, press Allow. AWS always asks for that click in this flow. |
| Always-on Mac used as a server (you are not at it) | remote |
As above. local would open a tab on a screen nobody looks at. |
Set the mode with /aws-sso local or /aws-sso remote (stored per device), or
with PI_AWS_SSO_MODE in .env. A mode set by command wins over .env.
Setup
1. Prerequisites
-
AWS_PROFILEin pi-devbox.envnames your Bedrock profile. Itssource_profilechain must end in a profile withsso_session = …, with a matching[sso-session …]block in~/.aws/config. Old-style profiles withsso_start_urldirectly in the profile cannot be renewed; convert them. -
~/.awsis available in the container (pi-devbox mounts it). -
localonly: the Docker host has the AWS CLI, and the container can reach the host over SSH. Check from the container:ssh -F ~/.ssh-local/config host 'exec "${SHELL:-/bin/sh}" -lc "aws --version"'
2. Push notifications (recommended; essential for remote)
-
Install the ntfy app (iOS/Android) on your phone.
-
Make a topic name that nobody can guess. Anyone who knows it can read your pushes. Generate it in your own terminal, not in a pi session (transcripts are archived):
echo "pi-aws-$(openssl rand -hex 12)" -
In the app, subscribe to that topic on
ntfy.sh(or on your own ntfy server).
3. .env (pi-devbox)
PI_AWS_SSO_NTFY=https://ntfy.sh/pi-aws-<your random part>
PI_AWS_SSO_MODE=remote # only on headless/remote devices
# PI_AWS_SSO_NTFY_TOKEN= # only for a protected topic on your own server
# PI_AWS_SSO_REFRESH=1 # force the keeper on without /aws-sso on
.env reaches the container through env_file. Recreate the container afterwards
(docker compose up -d); a running pi does not see new variables.
4. In pi
/aws-sso on # once per device; stored in ~/.pi/agent/aws-sso/state.json
/aws-sso remote # if this device should use the device-code flow
/aws-sso push-test # your phone should buzz
/aws-sso status
/aws-sso now # optional: a first login, to see the flow once
/aws-sso status shows, for example:
keeper: ON · local (default) · push: ntfy.sh (topic 3fb657b1) · auto-continue: on
sso-session: MySession (profile chain Bedrock → Base)
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)
The topic is shown only as a fingerprint, never in clear.
What happens when a session ends
- Detected: in the session's last hour the hourly check gets a token cut short at
the end (
probe … result: capped, thensession_end_seen), and the status line showsAWS ✓ 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. - 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".remote: the link and code appear in pi, and you get the push "AWS SSO approval needed" (tap to open).
- Done: the log shows
login_end … ok: true. If you were pushed about the login, a low-priority push "AWS SSO renewed" follows. A running task never noticed. - 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 (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.
Typing a prompt while the session is over, or /aws-sso now, starts a login at
once without waiting for the back-off.
The status line shows AWS ✓ since 22:19, AWS ⏳ approve ABCD-EFGH,
AWS ⏳ waiting for you in the browser or AWS ✗ session ended.
Reading the log
~/.pi/agent/aws-sso/events.jsonl: one JSON line per event. Tokens appear only as
sha256[:8] fingerprints.
| Event | Meaning |
|---|---|
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 |
push, push_failed |
Push sent (HTTP status) or not (error) |
request_failed_expired |
A request failed on an expired session (fallback path) |
auto_continue |
The keeper sent continue to resume the run |
Troubleshooting
| Symptom | Check |
|---|---|
Status AWS ✗ AWS_PROFILE is not set / profile … not found |
AWS_PROFILE in .env, then the profile chain in ~/.aws/config (step 1). |
local login ends with rc: 255 |
SSH to the host fails; run the check in step 1. |
local login hangs, no browser appears |
Linux/Windows host: the SSH session cannot reach the desktop browser. Set PI_AWS_SSO_LOCAL_CMD, or use remote. |
| No push arrives | push/push_failed lines in the log; the phone is subscribed to exactly that topic and server; the ntfy app is allowed to notify (iOS Focus / Android battery settings). /aws-sso push-test. |
| "another pi process is already running the login" | Several pi windows share one login; approve the one shown in the window that started it. A stale lock from a crashed pi is cleared automatically. |
Nothing happens in a task/fork child or pi -p |
By design: only interactive pi sessions act. |
| A run stopped and did not resume | auto_continue in the log? It fires at most once per 10 min, and /aws-sso continue off disables it. Type continue yourself. |
Security notes
- The keeper never handles passwords or MFA. It runs the official AWS CLI for logins, lets pi's own AWS SDK make its normal renewal attempt, and reads the token cache only for fingerprints and expiry times. Token values are never logged.
- The ntfy topic is a shared secret: keep it long and random, or use a protected
topic on your own server (
PI_AWS_SSO_NTFY_TOKEN). - A pushed device link does not give anyone your token. Whoever approves it signs the waiting CLI in with their own account. Still, approve only codes you expect, and check that the code in the browser matches the push.
Verified so far (mbp-m1-2020, macOS host)
| Item | Status |
|---|---|
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) |
| 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) |
| End read from the token cap 39 min ahead; login 45 s after it, silent, new full-hour session, no failed request | verified 2026-10-12 08:08–08:49 |
| 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 |