An expired token mid-task means you stop working and go click through an SSO portal. This one sees the error, drives the IAM Identity Center portal in Chrome, and drops short-lived creds into a shell-sourced dotenv.
ExpiredToken
InvalidClientTokenId
UnrecognizedClientException
Unable to locate credentials
You're three commands deep into something real and an aws call dies. Not because the work is wrong — because a one-hour token quietly ran out. Now the task is on hold while you go find a browser tab, expand an account tile, and copy-paste export lines. The interruption costs more than the credentials do.
Hand the whole errand to the agent. It reaches the portal in Chrome, verifies it opened the right account, copies the credential block, and writes it to ~/.cache/aws-sso-creds/<portal>-<account>.env at mode 600. It prints the path, the account ID from sts get-caller-identity, and the expiry — never the secrets.
Because that's work, and this is lazy — the good kind. Doing it the official way means an ~/.aws/config sso-session plus a named profile for every account you ever touch, configured up front and maintained forever. That's a chore you pay before you get to do the thing you actually wanted to do.
And it only pays off for tools that know how to resolve an AWS profile. This drops temporary credentials into environment variables — the one credential path that works with everything: the CLI, Terraform, boto3, an SDK, a one-off script that never heard of a profile. Nothing to configure per account, nothing to maintain, and the creds expire on their own in about an hour, so the lazy path is also the one that leaves nothing lying around.
It doesn't wait to be asked. Any of these in an aws call's output is enough:
ExpiredToken · InvalidClientTokenIdUnrecognizedClientException · Unable to locate credentialsThat's the selling point. The failure is the invocation — the thing that used to interrupt you now just fixes itself and the task keeps moving.
Consuming it is one line, because the Bash tool doesn't persist env between calls:
set -a && source ~/.cache/aws-sso-creds/<portal>-<account>.env && set +a && aws sts get-caller-identity
And the write itself carries two scars — both real, both bite:
# 1. the portal's code block is CRLF; a stray \r corrupts the # Authorization header -> "Invalid header value" # 2. the block has NO trailing newline, so AWS_REGION lands on the # end of AWS_SESSION_TOKEN -> InvalidClientTokenId { pbpaste | tr -d '\r' | sed -E 's/^export //; s/"//g'; echo; echo "AWS_REGION=us-east-1"; } > "$OUT"
A session token ending in -east-1 means bug #2 bit you. Debug by inspecting lengths, never values.
This is the generic, any-portal one: portal-agnostic by design, nothing hardcoded. Every portal fact — start URL, IdP, account IDs, cache prefix — resolves from either portals.md or a private per-org <org>-aws wrapper skill that pins one portal and hands the facts down. The copy in this public repo ships portals.md as a template with a fake example org; real org data never lands here.