Also: unprompted-login back-off now matches the guide (30 min, 1 h, 2 h, then 4 h; the first retry was 1 h); README links the guide.
9.4 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, 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.
- 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.
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.
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 · started ≈ 22:19 · 1-h token until 23:19 (next check from 23:14)
last check: 23:14 · refreshed
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 5 minutes before the hourly token runs out, AWS refuses the
renewal. Log:
probe … result: refused. - 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, 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. 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) |
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 (2026-10-11, mbp-m1-2020)
| 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) |
| Detection by refresh attempt before any request fails | 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 |