Skip to content

feat: offer to run aws sso login when SSO credentials expire - #919

Merged
cchu-ld merged 5 commits into
mainfrom
cchu/aws-devops-2-sso-login
Oct 21, 2026
Merged

cchu-ld merged 5 commits into
mainfrom
cchu/aws-devops-2-sso-login

Conversation

@cchu-ld

@cchu-ld cchu-ld commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Requirements

  • I have added test coverage for new or changed functionality
  • I have followed the repository's pull request submission guidelines
  • I have validated my changes against all supported platform versions

Related issues

None.

Describe the solution you've provided

  • Problem: If the SSO session of a profile expired, setup and status stopped with an error. You had to run aws sso login yourself, then run the command again.
    • Solution: If the SSO token is expired or not in the cache, setup and status ask to run aws sso login --profile <name>. If you answer yes, the command runs it and builds the AWS clients again. It asks only once.
  • Problem: A login does not fix every SSO error, for example a removed role assignment or a network error. A prompt for those errors leads to a login, a failed retry, and the same advice again.
    • Solution: Only an expired or missing token gets the prompt, as SSOTokenInvalid finds it. That covers sso_session profiles, which aws configure sso writes, and legacy sso_start_url profiles. A test uses a real sso_session config. Other errors show their cause. If the token is still not valid after the login, the error says so and shows the cause.
  • Problem: A run without a person at the keyboard must not stop at a question.
    • Solution: The command asks only in a terminal. It also asks with --output json, because the prompt goes to stderr and stdout keeps only the JSON. If the aws binary is not on the PATH, it does not ask.
  • Problem: This PR adds a second prompt. If each prompt puts its own buffer around stdin, the first prompt can take the lines for the next prompt. Then a pasted answer gets lost.
    • Solution: All prompts read from one stdin through sharedStdin. nextLine reads a file one byte at a time, so the next lines stay for the next prompt. readSecret still gets the real file, so the token prompt stays hidden.

clients.go reads the yesFlag constant. A later PR adds the --yes flag. Until then, yesSet returns false.

Describe alternatives you've considered

None.

Additional context

Testing: go test ./... and golangci-lint pass on this branch.

🤖 Generated with Claude Code


Note

Overview
setup and status now go through loadClients, which detects expired or missing SSO tokens and can run aws sso login --profile <name> after a stderr prompt (or automatically when setup passes --yes). Prompts run only when stdin and stderr are terminals, the profile is SSO-backed, SSOTokenInvalid matches, and the aws CLI is on PATH; client creation is retried once, with a clearer error if the token is still invalid after login.

Prompt and output behavior is tightened so multiple interactive steps share one stdin (sharedStdin / nextLine), canPrompt requires a TTY on stderr as well as stdin, and the LaunchDarkly service-token prompt during setup writes to stderr so JSON output stays clean.

Credential error messages no longer embed SDK causes that might leak secrets (e.g. credential_process output); non-SSO failures now point users at aws sts get-caller-identity instead. README documents the SSO login offer.

Reviewed by Cursor Bugbot for commit d019fca. Bugbot is set up for automated code reviews on this repo. Configure here.

@cchu-ld

cchu-ld commented Oct 20, 2026

Copy link
Copy Markdown
Contributor Author

bugbot run

@cchu-ld
cchu-ld force-pushed the cchu/aws-devops-2-sso-login branch from e06e5a5 to 5e39d8f Compare October 20, 2026 17:28

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

@cchu-ld
cchu-ld force-pushed the cchu/aws-devops-2-sso-login branch from 5e39d8f to 45d5b06 Compare October 20, 2026 17:47
@cchu-ld
cchu-ld force-pushed the cchu/aws-devops-2-sso-login branch 2 times, most recently from 4e8a268 to 75aa295 Compare October 20, 2026 18:36
@cchu-ld
cchu-ld removed this pull request from stack #924 October 20, 2026 18:38
@cchu-ld
cchu-ld added this pull request to stack #926 October 20, 2026 18:38
@cchu-ld
cchu-ld marked this pull request as ready for review October 20, 2026 18:44
@cchu-ld
cchu-ld requested a review from a team as a code owner October 20, 2026 18:45
@cchu-ld
cchu-ld requested review from wchieng and removed request for a team October 20, 2026 18:45

// nextLine reads up to and including the next newline. It reads a file one
// byte at a time, so the lines after it stay for the next prompt.
func nextLine(in io.Reader) (string, error) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolves cursor's comment from original PR: #875 (comment)

@cchu-ld
cchu-ld requested a review from a team October 20, 2026 18:52
@cchu-ld
cchu-ld force-pushed the cchu/aws-devops-2-sso-login branch from 75aa295 to c75a222 Compare October 20, 2026 19:02
Base automatically changed from cchu/aws-devops-1-regions-creds to main October 20, 2026 19:09
@ffantl-ld
ffantl-ld self-requested a review October 20, 2026 19:09
@cchu-ld
cchu-ld removed this pull request from stack #926 October 20, 2026 19:13
@cchu-ld
cchu-ld added this pull request to stack #929 October 20, 2026 19:13
"no AWS credentials for profile %q. Run `aws sso login --profile %s`, then run this command again",
sso, sso,
)
case useful && named:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The credential error can print the AWS secret key.

noCredentialsError puts the SDK cause in the message with %v. If a credential_process helper prints output that is not pure JSON, the SDK copies the raw output into that cause (processcreds/provider.go:190: "parse failed of process output: %s"). The output can contain SecretAccessKey.

To reproduce, use a helper that prints one warning line before valid JSON:

#!/bin/sh
echo "warning: token cache refreshed"
echo '{"Version":1,"AccessKeyId":"AKIAFAKEEXAMPLE","SecretAccessKey":"FAKE-SECRET-DO-NOT-USE"}'

ldcli aws-devops-agent status --profile leak then prints:

unable to get AWS credentials for profile "leak": failed to refresh cached credentials, process provider error: parse failed of process output: warning: token cache refreshed
{"Version":1,"AccessKeyId":"AKIAFAKEEXAMPLE","SecretAccessKey":"FAKE-SECRET-DO-NOT-USE"}
, error: invalid character 'w' looking for beginning of value

main has the same leak, because noCredsErr.Error() joins the cause. Suggestion: do not print the cause, and add a test with this helper. TestCredentialErrorHidesTheSDKCause covers only the IMDS cause.

@cchu-ld cchu-ld Oct 21, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Francisco! Commits cde1655c and 0641cae2 fixes this.

The credential error no longer prints the SDK cause. It names the command that shows the cause:

unable to get AWS credentials for profile "leak". Run `aws sts get-caller-identity --profile leak` to see the cause

TestCredentialErrorHidesTheProcessOutput runs thte helper through the real SDK. It fails without the fix. Three other tests now make sure that the cause is not in the message. The code can still get the cause with errors.Unwrap. The SSO advice did not change.

This reply is from Claude.

Comment thread cmd/aws_devops_agent/clients_test.go Outdated
assert.Equal(t, 1, f.calls)
}

// JSON runs never ask, like the setup plan.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: the comment says "JSON runs never ask, like the setup plan", but the test below asserts that a JSON run in a terminal does ask. Suggest: "A JSON run in a terminal asks. The prompt goes to stderr, so stdout keeps only the JSON."

@cchu-ld cchu-ld Oct 21, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Done in cde1655c

@Andrewjeska

Copy link
Copy Markdown
Contributor

A few things from reading through this PR — overall the design is clean and the test coverage is excellent, especially the real-SDK tests. These are mostly questions / smaller cleanups.

1. yesFlag is a dead constant in this PR

const yesFlag = "yes"
...
func yesSet(cmd *cobra.Command) bool {
    yes, err := cmd.Flags().GetBool(yesFlag)
    return err == nil && yes
}

No command registers the yes flag in this PR, so GetBool returns an error, yesSet is always false, and l.interactive() collapses to tty(). The PR body says a follow-up adds the flag — but as written, yesSet has no test that can exercise its true branch, and a reader has to grep across PRs to understand what the gate is for.

Would it be cleaner to either (a) register the flag here even if nothing else observes it yet, or (b) land yesSet + the flag together in the follow-up and keep this PR's interactive() as just tty()?

2. canPrompt was converted from func to var func

-func canPrompt() bool {
+var canPrompt = func() bool {

The new tests inject the tty callback through newSSOLogin and never patch the package-level var, so I can't see what the change enables. Package-level mutable function vars are a mild smell when nothing uses the mutability. Happy to be told I'm missing something — otherwise I'd revert to a plain func.

3. Prompting in --output json mode may surprise automation

TestLoginAsksInJSONModeInATerminal locks in that --output json still prompts. The rationale (prompt goes to stderr, stdout stays parseable) is clever, but it means a user doing

ldcli aws-devops-agent status --output json 2>/dev/null | jq ...

will see the command hang silently — stderr is discarded, no [y/N] ever renders, and jq is waiting on a stdout that never arrives. That's a real automation shape.

Two options I'd consider:

  • (a) Skip the prompt when --output json is set. Matches most CLI conventions and removes the edge case.
  • (b) Keep today's behavior but call it out in the command's long help and in the README paragraph you added — right now the only place this is written down is the test name.

4. Related: the eventual --yes flag will have inverted semantics as written

interactive: func() bool { return !yesSet(cmd) && tty() },
...
if name == "" || !awsdevops.SSOTokenInvalid(err) || !l.interactive() {
    return clients, err
}

Today yesSet is always false, so interactive collapses to tty. But once a follow-up registers --yes, this reads as:

--yes set → yesSet() true → interactive() false → bail out without attempting the login.

That's the opposite of the usual CLI meaning of --yes ("assume yes to prompts and proceed"). The structure conflates "will I prompt?" with "will I act?", so the follow-up PR will either (a) have --yes quietly do nothing useful, or (b) have to restructure this block. Something like:

if name == "" || !awsdevops.SSOTokenInvalid(err) { return clients, err }
if _, lookErr := l.lookPath("aws"); lookErr != nil { return clients, err }
if !yesSet(cmd) {
    if !tty() { return clients, err }
    // prompt; return if "no"
}
// login + retry

Worth getting right now so the follow-up is a 1-line flag addition.

via LD Research 🤖

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 1a571e3. Configure here.

Comment thread cmd/aws_devops_agent/prompt.go Resolved
@cchu-ld
cchu-ld force-pushed the cchu/aws-devops-2-sso-login branch from 1a571e3 to cde1655 Compare October 21, 2026 17:34
@cchu-ld

cchu-ld commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor Author

@Andrewjeska, thanks! Commits cde1655c and 0641cae2 on #919, and 55b9212c on #922, address the four points.

  1. yesFlag: A test now registers --yes on a test command, so the tests cover the true branch.
  2. canPrompt is a plain function again.
  3. JSON mode: canPrompt now needs a terminal on stdin and on stderr. With 2>/dev/null, the command does not ask and does not wait. It returns the credential error. If stderr is on the screen, a JSON run still asks. The question goes to stderr, so stdout keeps only the JSON. The README says this.
  4. --yes: I used your structure, with one change. The login runs only in a terminal, because a person must finish the sign-in in a browser. In a terminal, --yes runs aws sso login without a question. Without a terminal, the command never runs the login, also with --yes. The error then tells the user to run aws sso login --profile <name>. In feat: ask which agent space to use and confirm the setup plan #922, the help text for --yes is now "Ask no questions and accept every step".

This reply is from Claude.

cchu-ld and others added 5 commits October 21, 2026 14:32
If the SSO token of the profile is expired or not in the cache, setup
and status ask to run aws sso login --profile <name>. Other SSO errors,
for example a removed role assignment, do not get the question. If you
answer yes, the command runs the login and builds the AWS clients
again. If the token is still not valid, the error says so and shows the
cause. They ask only in a terminal, also with --output json, because
the prompt goes to stderr.

All prompts read from one stdin through sharedStdin. nextLine reads a
file one byte at a time, so a pasted answer stays for the next prompt.

clients.go reads the yesFlag constant. The --yes flag comes in a later
PR. Until then, yesSet returns false.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
If a credential_process prints output that is not valid JSON, the SDK
puts all of the output in the error. The output can contain the secret
key, so the credential error now shows a fixed message for this case.

The prompts need a terminal on stderr too. A run with stderr sent to a
file does not wait for an answer that the user cannot see.

canPrompt is a plain function again. A test covers --yes in yesSet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The credential error no longer shows the SDK cause, because a cause can
contain the output of a credential_process with the secret key. The
error now names the command that shows the cause:
aws sts get-caller-identity --profile <name>.

The SSO login runs only in a terminal, because a person must finish the
sign-in in a browser. In a terminal, --yes runs the login without a
question.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
canPrompt now needs a terminal on stderr. The token prompt went to
stdout, so a run with stderr sent to a file skipped a prompt that the
user could see. The prompt now goes to stderr, like the SSO prompt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
If the SSO token is still not valid after aws sso login, the error now
names only the profile. It still wraps the credential error, so
errors.Is matches ErrNoCredentials. Two tests that checked only their
own fake are gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cchu-ld
cchu-ld force-pushed the cchu/aws-devops-2-sso-login branch from d019fca to c6ee83b Compare October 21, 2026 21:32
@cchu-ld
cchu-ld merged commit 0f8b02b into main Oct 21, 2026
8 checks passed
@cchu-ld
cchu-ld deleted the cchu/aws-devops-2-sso-login branch October 21, 2026 21:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants