Skip to content

Fail fast on azd ai agent init when not logged in#7614

Open
therealjohn wants to merge 1 commit intoAzure:mainfrom
therealjohn:fix-7547
Open

Fail fast on azd ai agent init when not logged in#7614
therealjohn wants to merge 1 commit intoAzure:mainfrom
therealjohn:fix-7547

Conversation

@therealjohn
Copy link
Copy Markdown
Contributor

Problem: Running azd ai agent init without being authenticated lets the user proceed through manifest detection, project scaffolding (azd init -t), and environment creation before finally failing at subscription selection with "not logged in". This leaves the directory in a partially-initialized state.

Fix: Add an ensureLoggedIn check early in the init command's RunE, before any interactive prompts or file-modifying operations. It calls ListSubscriptions as a lightweight auth probe — if the user isn't authenticated, they see the error immediately with a suggestion to run azd auth login.

Fixes #7547

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds an early authentication probe to azd ai agent init so the command fails immediately when the user isn’t logged in, avoiding partially-initialized project state (Fixes #7547).

Changes:

  • Introduces ensureLoggedIn() which calls Account.ListSubscriptions and converts gRPC Unauthenticated into a structured auth error with a login suggestion.
  • Invokes ensureLoggedIn() near the start of init’s RunE, before prompts and file-modifying work.

Comment on lines +132 to +147
// only gRPC Unauthenticated errors are treated as failures. Other errors (e.g.
// network issues) are ignored so they don't block init for unrelated reasons.
func ensureLoggedIn(ctx context.Context, azdClient *azdext.AzdClient) error {
_, err := azdClient.Account().ListSubscriptions(ctx, &azdext.ListSubscriptionsRequest{})
if err == nil {
return nil
}

if st, ok := status.FromError(err); ok && st.Code() == codes.Unauthenticated {
return exterrors.Auth(
exterrors.CodeNotLoggedIn,
"not logged in",
"run `azd auth login` to authenticate before running init",
)
}

Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

ensureLoggedIn currently returns nil for all non-Unauthenticated errors, which also swallows cancellation/deadline errors (e.g., Ctrl+C or context timeout). That can cause init to continue after the user has cancelled. Consider explicitly propagating context cancellations (errors.Is(err, context.Canceled) or gRPC codes.Canceled/DeadlineExceeded) instead of returning nil.

Suggested change
// only gRPC Unauthenticated errors are treated as failures. Other errors (e.g.
// network issues) are ignored so they don't block init for unrelated reasons.
func ensureLoggedIn(ctx context.Context, azdClient *azdext.AzdClient) error {
_, err := azdClient.Account().ListSubscriptions(ctx, &azdext.ListSubscriptionsRequest{})
if err == nil {
return nil
}
if st, ok := status.FromError(err); ok && st.Code() == codes.Unauthenticated {
return exterrors.Auth(
exterrors.CodeNotLoggedIn,
"not logged in",
"run `azd auth login` to authenticate before running init",
)
}
// gRPC Unauthenticated errors are treated as login failures, cancellation and
// deadline errors are propagated, and other errors (e.g. network issues) are
// ignored so they don't block init for unrelated reasons.
func ensureLoggedIn(ctx context.Context, azdClient *azdext.AzdClient) error {
_, err := azdClient.Account().ListSubscriptions(ctx, &azdext.ListSubscriptionsRequest{})
if err == nil {
return nil
}
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
return err
}
if st, ok := status.FromError(err); ok {
if st.Code() == codes.Canceled || st.Code() == codes.DeadlineExceeded {
return err
}
if st.Code() == codes.Unauthenticated {
return exterrors.Auth(
exterrors.CodeNotLoggedIn,
"not logged in",
"run `azd auth login` to authenticate before running init",
)
}
}

Copilot uses AI. Check for mistakes.
Comment on lines +252 to +254
if err := ensureLoggedIn(ctx, azdClient); err != nil {
return err
}
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

The new fail-fast auth behavior via ensureLoggedIn is important user-facing flow control, but there’s no unit test asserting it. Consider adding a test that starts a minimal in-process gRPC server with AccountService.ListSubscriptions returning codes.Unauthenticated and verifies init errors out before doing any work; also cover the non-Unauthenticated path (should not block).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member

@jongio jongio left a comment

Choose a reason for hiding this comment

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

Clean fix for #7547. The early auth check prevents the user from going through manifest detection, scaffolding, and environment creation only to fail later at subscription selection. The function mirrors the checkAiModelServiceAvailable pattern right above it - consistent and easy to follow. One suggestion below.

Worth considering as a follow-up: azd core's AccountService doesn't have a dedicated CheckAuth or IsAuthenticated RPC. ListSubscriptions works as a probe here but it's indirect - it may enumerate all subscriptions just to verify auth. A lightweight auth-check method in core would give extensions a cleaner, cheaper way to gate on authentication, and could handle error classification (not_logged_in vs login_expired) server-side where the info is available.

Comment on lines +140 to +146
if st, ok := status.FromError(err); ok && st.Code() == codes.Unauthenticated {
return exterrors.Auth(
exterrors.CodeNotLoggedIn,
"not logged in",
"run `azd auth login` to authenticate before running init",
)
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The hardcoded "not logged in" message discards whatever the gRPC server actually reported. The exterrors package already has authFromGrpcMessage (unexported) that classifies "not logged in" vs "token expired" vs generic auth failure based on the server's message.

At minimum, forwarding st.Message() preserves useful diagnostic info:

Suggested change
if st, ok := status.FromError(err); ok && st.Code() == codes.Unauthenticated {
return exterrors.Auth(
exterrors.CodeNotLoggedIn,
"not logged in",
"run `azd auth login` to authenticate before running init",
)
}
if st, ok := status.FromError(err); ok && st.Code() == codes.Unauthenticated {
msg := st.Message()
if msg == "" {
msg = "not logged in"
}
return exterrors.Auth(
exterrors.CodeNotLoggedIn,
msg,
"run `azd auth login` to authenticate before running init",
)
}

Longer term, exporting authFromGrpcMessage (or wrapping it) would let callers here get proper code classification too.

Comment on lines +252 to +255
if err := ensureLoggedIn(ctx, azdClient); err != nil {
return err
}

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.

It might be better to arrange this after WaitForDebugger so debugging still works when logged out

// only gRPC Unauthenticated errors are treated as failures. Other errors (e.g.
// network issues) are ignored so they don't block init for unrelated reasons.
func ensureLoggedIn(ctx context.Context, azdClient *azdext.AzdClient) error {
_, err := azdClient.Account().ListSubscriptions(ctx, &azdext.ListSubscriptionsRequest{})
Copy link
Copy Markdown
Member

@vhvb1989 vhvb1989 Apr 9, 2026

Choose a reason for hiding this comment

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

this function ensureLogged in should not depend on trying to run one API that requires logged in.

If there is not a unique API to check for auth, consider using the workflow api like in

You you can call azd auth status here

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.

check if logged in before running azd ai agent init

5 participants