af-aws-exec
Runs one command with the short-lived credentials of one AWS profile. Only that command gets them. The usual flow is in Use AWS SSO.
Synopsis
Section titled “Synopsis”af-aws-exec --profile <name> [--account <id>] [--region <region>] [--login | --no-login] [--keep-aws-config] [-q] -- <command> [args...]af-aws-exec --listaf-aws-exec --helpaf-aws-exec --versionEverything after -- is the command to run. Give only one of --login and --no-login: when both are given, the last one wins.
Options
Section titled “Options”| Option | Meaning |
|---|---|
--profile <name> |
The profile to use. Required. --list shows the names |
--account <id> |
Refuse, running nothing, unless the profile is this AWS account. Required for a profile that is not one of your Settings profiles (one you defined in ~/.aws yourself) |
--region <region> |
The region for the command. How it is chosen: Region |
--login |
When the SSO login is not usable, run the device-code login in this terminal |
--no-login |
Never prompt: print the login command and exit with status 3 |
--keep-aws-config |
Give the command your own ~/.aws files and AWS_ENDPOINT_URL* settings as well |
-q |
Do not print the identity the command runs as |
--list |
Pull the profiles from Settings now and list the names you can use |
-h, --help |
Print the help |
--version |
Print the version |
Exit status
Section titled “Exit status”| Status | Meaning |
|---|---|
| The command’s own | When the command ran, its own exit status is returned as is |
| 1 | Any other refusal or failure (the account differs from --account, for example) |
| 2 | Usage error |
| 3 | The SSO login was needed but not started (--no-login, no approval in the Console in time, or Ctrl-C while waiting) |
| 4 | The profile asks for confirmation before use, and it was denied in the Console, not given in time, or the run belongs to no Agent Fleet session |
With status 4 the request stays in the Console. Allow it, then run the command again.
Output
Section titled “Output”Before running the command, it prints one line to standard error (-q turns it off):
af-aws-exec: running as arn:aws:sts::222222222222:assumed-role/AWSReservedSSO_Developer_…/you@example.com in region ap-northeast-1 (expires …)That line shows which account and role the command actually runs as. (expires …) appears only when the credentials carry an expiry; with no region set, the line reads in region (none).
Reading --list
Section titled “Reading --list”$ af-aws-exec --listSandbox 333333333333 Developer ("Sandbox")Staging 222222222222 Developer ("Staging")Deploy 444444444444 deploy ("Deploy", role assumed from Staging)Production 111111111111 Deployer ("Production", asks for confirmation before use: usable through af-aws-exec only, not in ~/.aws/config)prod-app (not exported: Settings labels prod app / prod-app all map to this name; rename all but one)The real output is tab-separated; here the columns are aligned with spaces.
One profile per line, tab-separated: name, account, role and label. The label is quoted inside parentheses.
- A profile that assumes a role shows
("label", role assumed from <source>) - A profile set to confirm before use says after its label that it is not written to
~/.aws/config - A profile that was not written to
~/.aws/configis listed after the others asname (not exported: reason), with no account or role columns. The reasons include two labels that map to the same name, a definition of the same name in your own~/.aws, a missing account or role, and a clash with a[DEFAULT]line
Profile names and ~/.aws/config
Section titled “Profile names and ~/.aws/config”Your profiles are also written into ~/.aws/config, so aws --profile <name>, the AWS SDKs and build tools
(Gradle, Maven, CDK, Terraform, …) can select them by name without you copying anything. Prefer --profile (or a
tool’s own profile setting) to AWS_PROFILE=<name>: keys already exported in the shell (AWS_ACCESS_KEY_ID and
friends) win over AWS_PROFILE, so it does not pin who a command runs as.
- The name is the profile’s label with every character other than letters, digits and
._@-replaced by-(labelprod app→ profileprod-app).af-aws-exec --listprints each name with its account, role and label; pick by those, not by the name alone. - If two labels map to the same name (
prod appandprod-app), neither is exported and--listsays so: either one could be the wrong account. Rename one of them in Settings. - The profiles sit in a managed block at the end of the file, between two
# agent-fleetmarker lines. Edit them in Settings, not inside the block: the block is rewritten, and so is anythingaws configure setwrites into it. Everything outside it is yours and is kept. Only profiles with both an account and a role are exported. With neither,aws --profile <name>would not use SSO at all and would quietly run as the workspace’s own role; with only one it would fail.--listsays which is missing. If a[DEFAULT]line in~/.aws/configwould break a profile (a different region, arole_arnthat would make every profile assume that role, …), that profile is not exported, andaf-aws-exec --listnames the line. If you already defined a profile with the same name yourself (in~/.aws/configor~/.aws/credentials), your definition is used and ours is left out. A profile labelleddefaultis never exported: it would change what every command without a profile runs as. - Changes arrive within about five minutes, at the next workspace start, or immediately when you run
af-aws-exec.
Logging in from a terminal
Section titled “Logging in from a terminal”The profile row’s Log in (above) does this in the Console. In a terminal, plain
aws sso login opens a callback on 127.0.0.1 inside the workspace, which
your browser cannot reach. Use the device-code flow instead:
aws sso login --profile <name> --use-device-code --no-browserOpen the URL it prints and approve the code. Approve only a code you started yourself just now. The login is shared with SSM sessions of the same profile, so logging in once covers both.
Why a command needs a profile
Section titled “Why a command needs a profile”In a container workspace (docker or AWS ECS, Agent Fleet 0.26.0 or later), your sessions and terminals get no AWS identity of their own:
- The workspace’s own role (the workload role) is withheld: the Agent keeps its credentials variables out of everything it starts.
- The machine’s identity is withheld too: the SDKs’ instance metadata lookup is switched off
(
AWS_EC2_METADATA_DISABLED=true). A tool that ignores that variable is stopped only by the host’s network block, which holds once your administrator has finished the 0.26.0 migration (Other operational controls). - A workspace that runs directly on your own machine is left as it is: an instance role there is your machine’s, and the SDKs still find it.
So what a command runs as depends on whether it names a profile:
| The command | What happens |
|---|---|
Names no profile (a bare aws …, an SDK’s default credential chain, a build tool with no profile setting) |
Fails with “Unable to locate credentials”, or its SDK’s wording |
| Names a profile that is misspelled or logged out | Fails with that profile’s own error |
| Names no profile, where your administrator lets the workspace use its own task role again (AWS ECS) | Quietly runs as that role, in another account |
“Unable to locate credentials” means name your profile, not “configure credentials”. Do not answer it with
aws configure, aws login (the CLI’s own hint) or keys in a [default] section of ~/.aws, and do not look for
credentials elsewhere. For deployments, writes and anything else whose account matters, pass your credentials
explicitly:
af-aws-exec --profile <name> -- ./gradlew deployaf-aws-exec --profile <name> -- npx cdk deployExample: a build tool with an S3 upload plugin
Section titled “Example: a build tool with an S3 upload plugin”A Gradle deploy task built on an AWS plugin (an S3 upload task, for instance) with no profile in the build script asks
the SDK’s default chain, in this order: environment variables, JVM system properties, the default profile in
~/.aws, then the container credentials and instance metadata. In a session, ./gradlew uploadArtifact therefore
stops with “Unable to load AWS credentials from any provider in the chain” instead of uploading as the workspace’s
role. Run it as you, naming the account:
af-aws-exec --profile <name> --account <id> --region <region> -- ./gradlew uploadArtifact- The plugin finds the profile’s short-lived credentials in its environment, the first place the chain looks.
- The “running as” line shows who uploads.
- If the build script names a profile itself (
profileName = "prod", say), see The config the command gets.
Every run shows in the session’s conversation
Section titled “Every run shows in the session’s conversation”Each af-aws-exec run leaves one line in the conversation of the session that ran it.
- What it shows: the profile, the account and role it ran as, the region, the command (
aws s3 ls, or just the program’s name), or why af-aws-exec refused to run it. Never the command’s arguments, which can carry secrets. - Where it sits: under the shell call that ran it, inside the turn’s work, so a turn with many runs keeps its
reply in view.
- Several runs of one call are one line with a count that opens to each run; a refused one is counted in red.
- Runs that no call can be matched to (a kind whose conversation records no times, a background process) are collapsed the same way between the turns, at the time they started.
- A shell call whose label is cut opens to the whole command it ran.
- What it does not show: how the command ended. af-aws-exec becomes the command, so its exit status is the one your shell sees.
- Every session’s runs: Settings > AWS profiles/SSM lists the latest runs of every session under “Recent runs”.
- It is not an audit. It is there so you can see what ran: the session is the one the run reported, which the
command can change, and a command run with plain
awsleaves no line.
Confirm before use
Section titled “Confirm before use”A profile can be set to “Confirm before use” in its form in Settings > AWS profiles/SSM. af-aws-exec then asks you
before that profile’s first use in each session, before it fetches any credential.
- Where it asks: a card in the session’s conversation, or a toast when that conversation is not on screen. It names the session, the profile, its account and role, with Allow for this session and Deny.
- An approval lasts 8 hours or until the session ends, and only for the profile as it is now: editing its account, role or portal asks again.
- Denied, or not answered in time: the command does not run and af-aws-exec exits with status 4. An unanswered request stays up for 15 minutes, so you can still allow it and the agent’s next run goes through.
- A role chained from such a profile asks for that profile too.
- Such a profile is not written to
~/.aws/config, so a plainaws --profilecannot use it: run it withaf-aws-exec.
What it does not do. It catches mistakes and use you would not notice, not an agent that sets out to get around it:
- An agent can answer the request through the workspace’s own interface (the conversation line then says “approved without the Console”).
- An agent can reuse a login that another profile on the same sign-in keeps open. A profile that stays behind a sign-in of its own, logged out after use, still needs your device-code approval every time.
Model route
Section titled “Model route”Using a profile for claude’s Amazon Bedrock model calls (the “Model route” mark in the profile’s form) is in Amazon Bedrock route.
A read-only lookup with aws --profile
Section titled “A read-only lookup with aws --profile”Deployments, writes, build tools, SDK programs and scripts stay with af-aws-exec, because it does what a plain
aws --profile does not:
- checks the account (
--account); - hands short-lived credentials to tools whose SDK cannot read an SSO profile (older SDKs such as the AWS SDK for Java v1, common in Gradle/Maven plugins);
- asks you in the Console when a login is missing;
- refuses a profile name that means different identities to different tools;
- pins the region with
--regionand removes endpoint overrides.
A quick look-up with the AWS CLI itself (describe-*, list-*, s3 ls) may name one of your Settings profiles
directly, but only in a shell where this check prints isolated. It prints one word and nothing of the environment
or of the error:
if err=$(env -u AWS_PROFILE -u AWS_DEFAULT_PROFILE AWS_IGNORE_CONFIGURED_ENDPOINT_URLS=true \ aws sts get-caller-identity 2>&1 >/dev/null); then st=0; else st=$?; fierr=${err#"${err%%[![:space:]]*}"}if [ "$st" = 253 ] && [ "$(printenv AWS_EC2_METADATA_DISABLED)" = true ] && [ "${AF_WS_WORKLOAD_AWS:-}" != 1 ] \ && [ -z "$(env | cut -d= -f1 | grep -E '^AWS_(CONTAINER_|CONFIG_FILE$|SHARED_CREDENTIALS_FILE$|ENDPOINT_URL)')" ] \ && case $err in "Unable to locate credentials"* | \ "aws: [ERROR]: An error occurred (NoCredentials): Unable to locate credentials"*) true ;; *) false ;; esacthen echo isolated; else echo not-isolated; fi; unset err stisolated means all of these hold, so a misspelled or logged-out profile fails instead of answering for another
account:
AWS_EC2_METADATA_DISABLED=trueis exported, so the CLI does not ask instance metadata;- the environment holds no workload credentials and no AWS config-file or endpoint overrides;
- the CLI’s default chain (with
AWS_PROFILEset aside and configured endpoints ignored) ended in its own “no credentials” error (exit status 253).
not-isolated is anything else: your own default credentials, an expired session, a failing
credential_process, a network error. A workspace on your own machine, a deployment that hands the task role back and
a workspace not started again since the upgrade normally print it too. Where it prints not-isolated, run lookups
through af-aws-exec too.
The check reads what this shell would hand the CLI; it does not prove the runtime, the version or the host’s network
block. Where it prints isolated, a direct lookup still needs all of these, or it goes through af-aws-exec:
- The profile is one
af-aws-exec --listshows as exported, chosen there by its account and role (not by the name alone). If you do not know which account, find out first.- When
--listwarns that its names come “from an earlier sync; not checked against Settings now”, nothing is verified: useaf-aws-exec. - A profile
--listdoes not show, marks as not exported, or one you defined yourself (role_arn,credential_process): useaf-aws-exec.
- When
- Name the region and ignore configured endpoints, with no
--endpoint-url:AWS_IGNORE_CONFIGURED_ENDPOINT_URLS=true aws --profile <name> --region <region> ec2 describe-instances. A staleAWS_REGIONin your shell beats the profile’s region, and anendpoint_urlin your~/.aws/config(left by an emulator setup, say) would send the request somewhere other than AWS. - Run it in the same shell you checked.
- An SSO token error means the login is missing. Plain
aws --profiledoes not ask the Console: rerun withaf-aws-exec, which does. - A profile
af-aws-execrefused is refused here too; plain--profileis no way around it.
Agents in the workspace follow the same rule.
What the command receives
Section titled “What the command receives”- The profile’s short-lived credentials, through that one command’s environment only.
af-aws-execitself writes them nowhere and prints nothing but the identity the command runs as. (The AWS CLI keeps its own login and role caches under~/.aws, as it always does.) - No way to the workload role. If the login is missing or expired, the command fails instead of falling back.
Inside a workspace, at a terminal too, it asks you in the Console (When the login is missing);
--loginruns the device-code login in that terminal instead. - Credentials last as long as the SSO role session, or the assumed role’s session (often one hour). A longer command fails when they expire rather than switching identity.
Settings profiles it accepts
Section titled “Settings profiles it accepts”A Settings profile must have an account and role. The credentials are obtained through the profile’s SSO login alone (from a minimal config holding only its SSO settings, with endpoint overrides ignored), then checked with AWS to be a session of that profile’s permission-set role in that account.
It is refused when the profile also carries:
| Also set | Why it is refused |
|---|---|
role_arn (even empty), or a web_identity_token_file path |
The AWS CLI would then not use the SSO login |
source_profile, credential_source, credential_process, static keys (in ~/.aws/config or ~/.aws/credentials), or an empty web_identity_token_file (next to role_arn or not) |
The AWS CLI would still use SSO, but other SDKs and tools use those first, so the one name would mean different identities to different tools |
- Keys under a
[DEFAULT]section count, since the CLI applies them to every profile, and so does an empty value. - A tool that syncs credentials into
~/.aws/credentialsunder the same name (yawsso, for example) causes this: sync to another name. --profile defaultis refused: name the SSO profile.- A name that means two things is refused: two Settings labels that map to it, or your own
~/.awsdefinition of a Settings profile’s name with a different account, role or sign-in portal.
Profiles you defined yourself
Section titled “Profiles you defined yourself”Some accounts are reached only through a profile of your own in ~/.aws: one that assumes a role from a
source_profile (role_arn + source_profile), or one with a credential_process. af-aws-exec runs such a profile
when you name its account with --account, which is required here:
af-aws-exec --profile deploy-target --account <id> --region <region> -- ./deploy.shHow it runs. The AWS CLI resolves the profile from your own files, with its own ways to the workload role (the container credentials variables, IMDS) and endpoint overrides switched off. The command runs only if AWS reports the credentials in that account (and, for a role, as a session of that role).
- A
credential_processis your own program and runs as you wrote it:af-aws-execchecks the account of what it returns, not where it came from. When it fails, its output is not shown; run it yourself to see why. - Only temporary credentials are passed. A profile that resolves to long-lived keys is refused: use the keys to assume a role instead.
- If the chain ends in an SSO profile whose login is missing, the command exits with code 3 and the
aws sso logincommand for that SSO profile (at a terminal it starts the login itself). The Console is not asked. - These profiles do not appear in
af-aws-exec --list, and they cannot share a name with a Settings profile. - The source keys stay in your
~/.awsfiles as before;af-aws-execnever hands them to the command.
It is refused, before anything is fetched, when:
- the profile, or any profile in its
source_profilechain (a[DEFAULT]section included), setscredential_source(that takes the workspace’s own credentials),web_identity_token_file,login_sessionormfa_serial(nobody can answer the MFA prompt when an agent runs the command); - a profile in the chain names more than one way to get credentials, for example keys (even only a session token, or
keys in
~/.aws/config) beside acredential_processor on a role profile: the AWS CLI and other SDKs would not agree on which to use; - the SSO profile the chain ends in is incomplete, or the chain is broken.
Keep one way per profile: keys go in a profile of their own, named as source_profile. The one exception is a role
profile you name that is its own source_profile and holds the keys itself.
When the login is missing
Section titled “When the login is missing”When a command needs the login (an agent’s, or yours in a shell session’s terminal), it asks you in the Console. This
covers your Settings profiles; for a profile you defined yourself, or with --no-login, the command exits with code 3
and the login command to run in a terminal.
A toast at the bottom of the screen names the profile, its account and role from Settings, and the session that asks. It counts down until the request is withdrawn: 15 minutes after a command last asked (no countdown while a sign-in for it runs).
- Press Log in on the toast to open the login window, and check what it is for.
- Press Log in there. Only then is a sign-in code created, and only that window shows it.
- Check the code, press Sign in and approve, and approve it on the page that opens.
The agent’s command waits about a minute and a half (a few seconds when it is not run by an agent session, such as a script in a shell session) and continues once you approve. If it has given up by then, the agent runs it again.
| On the toast | What happens |
|---|---|
| Close | Keeps the request: it stays in the Console on your other devices too. On a device whose browser cannot sign in, close it and press Log in in the Console on another one |
| Cancel the request | For a login you do not want: withdraws the request, and for about a minute that profile is not asked for again |
| Closing the toast | Only hides it in that tab |
Log in on the profile’s row in Settings works at any time, also during that minute.
At your own terminal (a shell or SSM session):
- The command prints that it asked the Console and waits up to ten minutes, as long as the device code lives, for you to approve there, then carries on without a rerun.
- Ctrl-C stops waiting: the command exits with code 3 and the request stays in the Console.
--loginruns the device-code login in that terminal instead (it prints the URL and code there). That is also what happens when the Console cannot be asked at all, and for the profiles the Console does not cover.- Outside a workspace, the terminal login starts at a terminal, and the command exits with code 3 without one.
The config the command gets
Section titled “The config the command gets”The command gets an AWS config that defines only the profile you chose (it hands back the same short-lived
credentials), no credentials file, and no AWS_ENDPOINT_URL* overrides.
| The tool inside the command | Result |
|---|---|
| Names no profile, or the same profile | Works |
Names a different one (Terraform’s profile = "staging", cdk deploy --profile staging, AWS_PROFILE=staging in a script) |
Fails with “The config profile (staging) could not be found”, instead of quietly running as that other profile |
Replaces AWS_ACCESS_KEY_ID (after an assume-role, say) |
The chosen profile stops resolving there rather than quietly meaning the new account: use the new credentials without --profile |
- When you see “could not be found”, remove that profile setting from the tool, or run it under that profile
(
af-aws-exec --profile staging …). - Do not add
--keep-aws-configto get past it: that hands the tool your own~/.awsfiles and endpoint settings again, and it would then run as the profile it names. Keep--keep-aws-configfor tools that need other settings from those files. - If
af-aws-execcannot keep that config private (a home directory other users can write to, for example), it gives the command an empty AWS config instead, with a warning. The command stays isolated; only a tool naming the same profile will not find it.
Region
Section titled “Region”--region, if you give it- otherwise a region already exported in your shell:
AWS_REGION, thenAWS_DEFAULT_REGION - otherwise the profile’s
The command gets it in both AWS_REGION and AWS_DEFAULT_REGION, and the “running as” line shows it. A stale
AWS_REGION in your shell beats the profile’s region, so give --region for deployments.
--account
Section titled “--account”--account <id> refuses to run unless the profile is that AWS account. Put it in scripts, runbooks and agent
instructions for anything that deploys, so a wrong profile name stops before anything happens. For a profile that is
not one of your Settings profiles (one you defined yourself), --account is required.