Files

10 KiB
Raw Permalink Blame History

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_PROFILE in pi-devbox .env names your Bedrock profile. Its source_profile chain must end in a profile with sso_session = …, with a matching [sso-session …] block in ~/.aws/config. Old-style profiles with sso_start_url directly in the profile cannot be renewed; convert them.

  • ~/.aws is available in the container (pi-devbox mounts it).

  • local only: 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"'
    
  1. Install the ntfy app (iOS/Android) on your phone.

  2. 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)"
    
  3. 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

  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".
    • remote: the link and code appear in pi, and you get the push "AWS SSO approval needed" (tap to open).
  3. 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.
  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 (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