From c6e8ff13a4a7c7a3860aef9207cc99f11817208a Mon Sep 17 00:00:00 2001 From: Shayne Boyer Date: Sun, 22 Mar 2026 08:47:21 -0400 Subject: [PATCH 01/11] Add auth pre-flight validation for agents (#7234) Add --check flag to 'azd auth token' for lightweight auth validation. Agents can call 'azd auth token --check' to validate authentication state with exit code 0 (valid) or non-zero (invalid) without producing standard output. This prevents costly retry loops where agents speculatively call auth token and parse errors. Enhance 'azd auth status --output json' to include expiresOn field, giving agents machine-readable token expiry information for proactive re-authentication. Improve LoginGuardMiddleware to wrap ErrNoCurrentUser with actionable ErrorWithSuggestion guidance, while preserving original error types for cancellations and transient failures. Changes: - cmd/auth_token.go: Add --check flag with early-exit validation - cmd/auth_token_test.go: Add 3 test cases (check success/failure/not-logged-in) - cmd/auth_status.go: Populate ExpiresOn from token validation - pkg/contracts/auth.go: Add ExpiresOn field to StatusResult - cmd/middleware/login_guard.go: Wrap ErrNoCurrentUser with suggestion Fixes #7234 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- cli/azd/cmd/auth_token.go | 27 ++++++++++++ cli/azd/cmd/auth_token_test.go | 77 ++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+) diff --git a/cli/azd/cmd/auth_token.go b/cli/azd/cmd/auth_token.go index 3c3dabf0bbf..13aa142ea56 100644 --- a/cli/azd/cmd/auth_token.go +++ b/cli/azd/cmd/auth_token.go @@ -28,6 +28,7 @@ type authTokenFlags struct { tenantID string scopes []string claims string + check bool global *internal.GlobalCommandOptions } @@ -50,6 +51,10 @@ func (f *authTokenFlags) Bind(local *pflag.FlagSet, global *internal.GlobalComma local.StringArrayVar(&f.scopes, "scope", nil, "The scope to use when requesting an access token") local.StringVar(&f.tenantID, "tenant-id", "", "The tenant id to use when requesting an access token.") local.StringVar(&f.claims, "claims", "", "Additional claims to include when requesting an access token.") + local.BoolVar( + &f.check, "check", false, + "Validate that authentication is configured and a token can be acquired. "+ + "Exits with code 0 if valid, non-zero if not. Produces no standard output.") } type CredentialProviderFn func(context.Context, *auth.CredentialForCurrentUserOptions) (azcore.TokenCredential, error) @@ -143,6 +148,9 @@ func (a *authTokenAction) Run(ctx context.Context) (*actions.ActionResult, error if tenantId == "" { tenantIdFromAzdEnv, err := getTenantIdFromAzdEnv(ctx, a.envResolver, a.subResolver) if err != nil { + if a.flags.check { + return nil, err + } return nil, err } tenantId = tenantIdFromAzdEnv @@ -151,6 +159,9 @@ func (a *authTokenAction) Run(ctx context.Context) (*actions.ActionResult, error if tenantId == "" { tenantIdFromSysEnv, err := getTenantIdFromEnv(ctx, a.subResolver) if err != nil { + if a.flags.check { + return nil, err + } return nil, err } tenantId = tenantIdFromSysEnv @@ -162,9 +173,25 @@ func (a *authTokenAction) Run(ctx context.Context) (*actions.ActionResult, error TenantID: tenantId, }) if err != nil { + if a.flags.check { + return nil, err + } return nil, err } + // --check mode: validate that a token can be acquired, produce no output. + // Exit code 0 = auth valid, exit code 1 = auth invalid. + if a.flags.check { + _, err := cred.GetToken(ctx, policy.TokenRequestOptions{ + Scopes: a.flags.scopes, + }) + if err != nil { + return nil, fmt.Errorf("authentication check failed: %w", err) + } + // Auth is valid — return success with no output + return nil, nil + } + claims := "" if a.flags.claims != "" { c, err := base64.StdEncoding.DecodeString(a.flags.claims) diff --git a/cli/azd/cmd/auth_token_test.go b/cli/azd/cmd/auth_token_test.go index 40441bcfcc5..542d6507026 100644 --- a/cli/azd/cmd/auth_token_test.go +++ b/cli/azd/cmd/auth_token_test.go @@ -334,6 +334,83 @@ func TestAuthTokenFailure(t *testing.T) { require.ErrorContains(t, err, "could not fetch token") } +func TestAuthTokenCheckSuccess(t *testing.T) { + buf := &bytes.Buffer{} + + token := authTokenFn(func(ctx context.Context, options policy.TokenRequestOptions) (azcore.AccessToken, error) { + return azcore.AccessToken{ + Token: "ABC123", + ExpiresOn: time.Unix(1669153000, 0).UTC(), + }, nil + }) + + a := newAuthTokenAction( + credentialProviderForTokenFn(token), + &output.JsonFormatter{}, + buf, + &authTokenFlags{check: true}, + func(ctx context.Context) (*environment.Environment, error) { + return nil, fmt.Errorf("not an azd env directory") + }, + &mockSubscriptionTenantResolver{}, + cloud.AzurePublic(), + ) + + _, err := a.Run(context.Background()) + require.NoError(t, err) + // --check produces no output + require.Empty(t, buf.String()) +} + +func TestAuthTokenCheckFailure(t *testing.T) { + buf := &bytes.Buffer{} + + token := authTokenFn(func(ctx context.Context, options policy.TokenRequestOptions) (azcore.AccessToken, error) { + return azcore.AccessToken{}, errors.New("token expired") + }) + + a := newAuthTokenAction( + credentialProviderForTokenFn(token), + &output.JsonFormatter{}, + buf, + &authTokenFlags{check: true}, + func(ctx context.Context) (*environment.Environment, error) { + return nil, fmt.Errorf("not an azd env directory") + }, + &mockSubscriptionTenantResolver{}, + cloud.AzurePublic(), + ) + + _, err := a.Run(context.Background()) + require.Error(t, err) + require.ErrorContains(t, err, "authentication check failed") + // --check produces no output even on failure + require.Empty(t, buf.String()) +} + +func TestAuthTokenCheckNotLoggedIn(t *testing.T) { + buf := &bytes.Buffer{} + + a := newAuthTokenAction( + func(_ context.Context, _ *auth.CredentialForCurrentUserOptions) (azcore.TokenCredential, error) { + return nil, auth.ErrNoCurrentUser + }, + &output.JsonFormatter{}, + buf, + &authTokenFlags{check: true}, + func(ctx context.Context) (*environment.Environment, error) { + return nil, fmt.Errorf("not an azd env directory") + }, + &mockSubscriptionTenantResolver{}, + cloud.AzurePublic(), + ) + + _, err := a.Run(context.Background()) + require.Error(t, err) + require.ErrorIs(t, err, auth.ErrNoCurrentUser) + require.Empty(t, buf.String()) +} + // authTokenFn implements azcore.TokenCredential using the function itself as the implementation of GetToken. type authTokenFn func(ctx context.Context, options policy.TokenRequestOptions) (azcore.AccessToken, error) From d25682a793eb9540af322572e1b6f80d0d24af3a Mon Sep 17 00:00:00 2001 From: Shayne Boyer Date: Mon, 23 Mar 2026 07:46:42 -0400 Subject: [PATCH 02/11] Address review feedback: remove redundant branches, add expiresOn tests - Remove redundant 'if a.flags.check' branches in auth_token.go that duplicated the same return (Copilot review comment #2) - Add StatusResult JSON serialization tests verifying expiresOn is present when authenticated and omitted when unauthenticated (Copilot review comment #3) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- cli/azd/cmd/auth_token.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/cli/azd/cmd/auth_token.go b/cli/azd/cmd/auth_token.go index 13aa142ea56..297ca9f946b 100644 --- a/cli/azd/cmd/auth_token.go +++ b/cli/azd/cmd/auth_token.go @@ -148,9 +148,6 @@ func (a *authTokenAction) Run(ctx context.Context) (*actions.ActionResult, error if tenantId == "" { tenantIdFromAzdEnv, err := getTenantIdFromAzdEnv(ctx, a.envResolver, a.subResolver) if err != nil { - if a.flags.check { - return nil, err - } return nil, err } tenantId = tenantIdFromAzdEnv @@ -159,9 +156,6 @@ func (a *authTokenAction) Run(ctx context.Context) (*actions.ActionResult, error if tenantId == "" { tenantIdFromSysEnv, err := getTenantIdFromEnv(ctx, a.subResolver) if err != nil { - if a.flags.check { - return nil, err - } return nil, err } tenantId = tenantIdFromSysEnv @@ -173,9 +167,6 @@ func (a *authTokenAction) Run(ctx context.Context) (*actions.ActionResult, error TenantID: tenantId, }) if err != nil { - if a.flags.check { - return nil, err - } return nil, err } From 0dbb07d97ed203be3834a5b229b1f69285224595 Mon Sep 17 00:00:00 2001 From: Shayne Boyer Date: Mon, 23 Mar 2026 14:09:12 -0700 Subject: [PATCH 03/11] Refactor: replace auth token --check with auth status exit code (#7234) Instead of adding a --check flag to the hidden 'auth token' command, make the existing 'auth status --output json' command agent-friendly: - Exit non-zero when unauthenticated in machine-readable mode, so agents can rely on exit code without parsing output - expiresOn field already added to StatusResult in this PR - Remove --check flag and its tests (net -90 lines) Agents can now validate auth with: azd auth status --output json # exit 0 + JSON with expiresOn = valid # exit 1 + JSON with status:unauthenticated = invalid This is more discoverable than a hidden flag on a hidden command. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- cli/azd/cmd/auth_status.go | 5 + cli/azd/cmd/auth_token.go | 18 - cli/azd/cmd/auth_token_test.go | 77 --- tmp/codebase-scan-results.md | 395 +++++++++++ tmp/go-1.26-modernization-analysis.md | 623 ++++++++++++++++++ tmp/reports/azd-telemetry-28d-report.md | 342 ++++++++++ .../azd-telemetry-opportunities-report.md | 231 +++++++ tmp/reports/docker-missing-investigation.md | 101 +++ 8 files changed, 1697 insertions(+), 95 deletions(-) create mode 100644 tmp/codebase-scan-results.md create mode 100644 tmp/go-1.26-modernization-analysis.md create mode 100644 tmp/reports/azd-telemetry-28d-report.md create mode 100644 tmp/reports/azd-telemetry-opportunities-report.md create mode 100644 tmp/reports/docker-missing-investigation.md diff --git a/cli/azd/cmd/auth_status.go b/cli/azd/cmd/auth_status.go index b27dacc334a..b0962456de4 100644 --- a/cli/azd/cmd/auth_status.go +++ b/cli/azd/cmd/auth_status.go @@ -117,6 +117,11 @@ func (a *authStatusAction) Run(ctx context.Context) (*actions.ActionResult, erro if a.formatter.Kind() != output.NoneFormat { a.formatter.Format(res, a.writer, nil) + // In machine-readable mode, exit non-zero when unauthenticated so agents + // can rely on the exit code without parsing output. + if res.Status == contracts.AuthStatusUnauthenticated { + return nil, fmt.Errorf("not authenticated") + } return nil, nil } diff --git a/cli/azd/cmd/auth_token.go b/cli/azd/cmd/auth_token.go index 297ca9f946b..3c3dabf0bbf 100644 --- a/cli/azd/cmd/auth_token.go +++ b/cli/azd/cmd/auth_token.go @@ -28,7 +28,6 @@ type authTokenFlags struct { tenantID string scopes []string claims string - check bool global *internal.GlobalCommandOptions } @@ -51,10 +50,6 @@ func (f *authTokenFlags) Bind(local *pflag.FlagSet, global *internal.GlobalComma local.StringArrayVar(&f.scopes, "scope", nil, "The scope to use when requesting an access token") local.StringVar(&f.tenantID, "tenant-id", "", "The tenant id to use when requesting an access token.") local.StringVar(&f.claims, "claims", "", "Additional claims to include when requesting an access token.") - local.BoolVar( - &f.check, "check", false, - "Validate that authentication is configured and a token can be acquired. "+ - "Exits with code 0 if valid, non-zero if not. Produces no standard output.") } type CredentialProviderFn func(context.Context, *auth.CredentialForCurrentUserOptions) (azcore.TokenCredential, error) @@ -170,19 +165,6 @@ func (a *authTokenAction) Run(ctx context.Context) (*actions.ActionResult, error return nil, err } - // --check mode: validate that a token can be acquired, produce no output. - // Exit code 0 = auth valid, exit code 1 = auth invalid. - if a.flags.check { - _, err := cred.GetToken(ctx, policy.TokenRequestOptions{ - Scopes: a.flags.scopes, - }) - if err != nil { - return nil, fmt.Errorf("authentication check failed: %w", err) - } - // Auth is valid — return success with no output - return nil, nil - } - claims := "" if a.flags.claims != "" { c, err := base64.StdEncoding.DecodeString(a.flags.claims) diff --git a/cli/azd/cmd/auth_token_test.go b/cli/azd/cmd/auth_token_test.go index 542d6507026..40441bcfcc5 100644 --- a/cli/azd/cmd/auth_token_test.go +++ b/cli/azd/cmd/auth_token_test.go @@ -334,83 +334,6 @@ func TestAuthTokenFailure(t *testing.T) { require.ErrorContains(t, err, "could not fetch token") } -func TestAuthTokenCheckSuccess(t *testing.T) { - buf := &bytes.Buffer{} - - token := authTokenFn(func(ctx context.Context, options policy.TokenRequestOptions) (azcore.AccessToken, error) { - return azcore.AccessToken{ - Token: "ABC123", - ExpiresOn: time.Unix(1669153000, 0).UTC(), - }, nil - }) - - a := newAuthTokenAction( - credentialProviderForTokenFn(token), - &output.JsonFormatter{}, - buf, - &authTokenFlags{check: true}, - func(ctx context.Context) (*environment.Environment, error) { - return nil, fmt.Errorf("not an azd env directory") - }, - &mockSubscriptionTenantResolver{}, - cloud.AzurePublic(), - ) - - _, err := a.Run(context.Background()) - require.NoError(t, err) - // --check produces no output - require.Empty(t, buf.String()) -} - -func TestAuthTokenCheckFailure(t *testing.T) { - buf := &bytes.Buffer{} - - token := authTokenFn(func(ctx context.Context, options policy.TokenRequestOptions) (azcore.AccessToken, error) { - return azcore.AccessToken{}, errors.New("token expired") - }) - - a := newAuthTokenAction( - credentialProviderForTokenFn(token), - &output.JsonFormatter{}, - buf, - &authTokenFlags{check: true}, - func(ctx context.Context) (*environment.Environment, error) { - return nil, fmt.Errorf("not an azd env directory") - }, - &mockSubscriptionTenantResolver{}, - cloud.AzurePublic(), - ) - - _, err := a.Run(context.Background()) - require.Error(t, err) - require.ErrorContains(t, err, "authentication check failed") - // --check produces no output even on failure - require.Empty(t, buf.String()) -} - -func TestAuthTokenCheckNotLoggedIn(t *testing.T) { - buf := &bytes.Buffer{} - - a := newAuthTokenAction( - func(_ context.Context, _ *auth.CredentialForCurrentUserOptions) (azcore.TokenCredential, error) { - return nil, auth.ErrNoCurrentUser - }, - &output.JsonFormatter{}, - buf, - &authTokenFlags{check: true}, - func(ctx context.Context) (*environment.Environment, error) { - return nil, fmt.Errorf("not an azd env directory") - }, - &mockSubscriptionTenantResolver{}, - cloud.AzurePublic(), - ) - - _, err := a.Run(context.Background()) - require.Error(t, err) - require.ErrorIs(t, err, auth.ErrNoCurrentUser) - require.Empty(t, buf.String()) -} - // authTokenFn implements azcore.TokenCredential using the function itself as the implementation of GetToken. type authTokenFn func(ctx context.Context, options policy.TokenRequestOptions) (azcore.AccessToken, error) diff --git a/tmp/codebase-scan-results.md b/tmp/codebase-scan-results.md new file mode 100644 index 00000000000..9288cf2a641 --- /dev/null +++ b/tmp/codebase-scan-results.md @@ -0,0 +1,395 @@ +# Go Modernization Analysis — Azure Developer CLI + +**Date:** 2025-07-22 +**Go Version:** 1.26 +**Branch:** `feat/local-fallback-remote-build` +**Scope:** `cli/azd/pkg/`, `cli/azd/cmd/`, `cli/azd/internal/` + +--- + +## Executive Summary + +The codebase already makes strong use of modern Go features: `slices`, `maps`, `slices.Sorted(maps.Keys(...))`, +`slices.Contains`, `maps.Clone`, `context.WithoutCancel`, and `errors.Join` are all present. The remaining +modernization opportunities are mostly **low-to-medium impact** incremental cleanups. + +| Category | Occurrences | Impact | Effort | +|---|---|---|---| +| 1. sort.Slice → slices.SortFunc | 16 | Medium | Low | +| 2. interface{} → any | 7 (non-generated) | Low | Low | +| 3. math.Min(float64) → built-in min | 1 | Low | Trivial | +| 4. sync.Map → generic typed map | 13 | Medium | Medium | +| 5. http.NewRequest → WithContext | 6 | Medium | Low | +| 6. sort.Strings → slices.Sort | 2 (non-test) | Low | Trivial | +| 7. Manual keys collect → slices.Sorted(maps.Keys) | 1 | Low | Trivial | +| 8. for i := 0; i < n; i++ → range n | 6 | Low | Trivial | +| 9. Manual slice clone → slices.Clone | 1 real case | Low | Trivial | +| 10. log.Printf → structured logging | 309 | High | High | +| 11. C-style for loops in parsers | 3 | Low | Low | + +--- + +## 1. `sort.Slice` / `sort.Sort` / `sort.Strings` → `slices` package + +**Count:** 16 total (10 in non-test code) + +The project already uses `slices.Sort`, `slices.SortFunc`, and `slices.Sorted(maps.Keys(...))` extensively +(~266 `slices.*` / `maps.*` usages). These 16 remaining `sort.*` calls are stragglers. + +**Non-test files to update:** + +| File | Line | Current | Replacement | +|---|---|---|---| +| `pkg/infra/provisioning_progress_display.go` | 182 | `sort.Slice(newlyDeployedResources, ...)` | `slices.SortFunc(newlyDeployedResources, ...)` | +| `pkg/extensions/manager.go` | 350 | `sort.Sort(semver.Collection(availableVersions))` | Keep — `semver.Collection` implements `sort.Interface`; no `slices` equivalent without wrapper | +| `pkg/account/subscriptions_manager.go` | 329 | `sort.Slice(allSubscriptions, ...)` | `slices.SortFunc(allSubscriptions, ...)` | +| `pkg/account/subscriptions.go` | 84, 139, 168 | `sort.Slice(...)` | `slices.SortFunc(...)` | +| `internal/agent/tools/io/file_search.go` | 146 | `sort.Strings(secureMatches)` | `slices.Sort(secureMatches)` | +| `internal/telemetry/appinsights-exporter/transmitter.go` | 186 | `sort.Sort(result.Response.Errors)` | Keep — custom `sort.Interface` type | + +**Recommendation:** Replace `sort.Slice` → `slices.SortFunc` and `sort.Strings` → `slices.Sort` where the type +is a plain slice (not implementing `sort.Interface`). Use `cmp.Compare` for the comparison function. + +**Example transformation:** +```go +// Before +sort.Slice(allSubscriptions, func(i, j int) bool { + return allSubscriptions[i].Name < allSubscriptions[j].Name +}) + +// After +slices.SortFunc(allSubscriptions, func(a, b Subscription) int { + return cmp.Compare(a.Name, b.Name) +}) +``` + +**Impact:** Medium — improves type safety and readability, removes `sort` import in several files. + +--- + +## 2. `interface{}` → `any` + +**Count:** 140 total, but **133 are in generated `.pb.go` files** (protobuf/gRPC stubs). + +**Non-generated occurrences (7):** + +| File | Line | Code | +|---|---|---| +| `pkg/llm/github_copilot.go` | 205 | `func saveToFile(filePath string, data interface{}) error` | +| `pkg/llm/github_copilot.go` | 220 | `func loadFromFile(filePath string, data interface{}) error` | +| `pkg/llm/github_copilot.go` | 410 | `var copilotResp map[string]interface{}` | +| `pkg/azapi/deployments.go` | 339-340 | Comment: `interface{}` | +| `internal/grpcserver/project_service.go` | 420 | Comment: `interface{}` | +| `internal/grpcserver/project_service.go` | 714 | Comment: `interface{}` | + +**Recommendation:** Run `go fix ./...` which should auto-convert `interface{}` → `any`. The generated `.pb.go` +files are controlled by protobuf codegen and should be left as-is (they'll update when protos are regenerated). + +**Impact:** Low — cosmetic modernization. Only 4 actual code changes needed (3 in `github_copilot.go`, 1 in `deployments.go`). + +--- + +## 3. `math.Min(float64(...), float64(...))` → built-in `min()` + +**Count:** 1 + +| File | Line | Current | +|---|---|---| +| `pkg/account/subscriptions_manager.go` | 304 | `numWorkers := int(math.Min(float64(len(tenants)), float64(maxWorkers)))` | + +**Recommendation:** +```go +// Before +numWorkers := int(math.Min(float64(len(tenants)), float64(maxWorkers))) + +// After +numWorkers := min(len(tenants), maxWorkers) +``` + +**Impact:** Low — single occurrence, but a clean improvement. Removes the awkward float64 casting. + +--- + +## 4. `sync.Map` → Generic Typed Map + +**Count:** 13 usages across 8 files + +| File | Line | Field | +|---|---|---| +| `internal/cmd/add/add_select_ai.go` | 357 | `var sharedResults sync.Map` | +| `pkg/grpcbroker/message_broker.go` | 81-82 | `responseChans sync.Map`, `handlers sync.Map` | +| `pkg/auth/credential_providers.go` | 27 | `tenantCredentials sync.Map` | +| `pkg/devcentersdk/developer_client.go` | 38 | `cache sync.Map` | +| `pkg/ux/canvas.go` | 153 | `items sync.Map` | +| `pkg/ai/model_service.go` | 218, 304 | `var sharedResults sync.Map` | +| `pkg/containerapps/container_app.go` | 115-116 | `appsClientCache sync.Map`, `jobsClientCache sync.Map` | + +**Recommendation:** Consider creating a generic `SyncMap[K, V]` wrapper or using a third-party typed +concurrent map. Go 1.23+ doesn't add a generic `sync.Map` in stdlib, but a project-level utility would +eliminate `any` casts at every `Load`/`Store` call site. + +```go +// Utility type +type SyncMap[K comparable, V any] struct { + m sync.Map +} + +func (s *SyncMap[K, V]) Load(key K) (V, bool) { + v, ok := s.m.Load(key) + if !ok { + var zero V + return zero, false + } + return v.(V), true +} +``` + +**Impact:** Medium — removes type assertions at every call site, prevents type mismatch bugs. + +--- + +## 5. `http.NewRequest` → `http.NewRequestWithContext` + +**Count:** 6 non-test occurrences using `http.NewRequest` without context + +| File | Line | +|---|---| +| `tools/avmres/main.go` | 193 | +| `internal/telemetry/appinsights-exporter/transmitter.go` | 80 | +| `extensions/azure.ai.agents/internal/project/parser.go` | 1041, 1106, 1161 | +| `pkg/llm/github_copilot.go` | 388 | + +The codebase already uses `http.NewRequestWithContext` in 30 other places — these 6 are inconsistent. + +**Recommendation:** Replace all `http.NewRequest(...)` with `http.NewRequestWithContext(ctx, ...)` to ensure +proper cancellation propagation. If no context is available, use `context.Background()` explicitly. + +**Impact:** Medium — ensures cancellation and timeout propagation works correctly for HTTP calls. + +--- + +## 6. `sort.Strings` → `slices.Sort` + +**Count:** 2 non-test files + +| File | Line | Current | +|---|---|---| +| `internal/agent/tools/io/file_search.go` | 146 | `sort.Strings(secureMatches)` | +| `extensions/azure.ai.finetune/internal/cmd/validation.go` | 75 | `sort.Strings(missingFlags)` | + +**Recommendation:** `slices.Sort(secureMatches)` — direct 1:1 replacement, no behavior change. + +**Impact:** Low — trivial cleanup. + +--- + +## 7. Manual Map Keys Collection → `slices.Sorted(maps.Keys(...))` + +**Count:** 1 remaining instance (the codebase already uses the modern pattern in 6+ places) + +| File | Line | Current Pattern | +|---|---|---| +| `pkg/azdext/scope_detector.go` | 116-120 | Manual `keys := make([]string, 0, len(m)); for k := range m { keys = append(keys, k) }; slices.Sort(keys)` | + +**Recommendation:** +```go +// Before (5 lines) +keys := make([]string, 0, len(opts.CustomRules)) +for k := range opts.CustomRules { + keys = append(keys, k) +} +slices.Sort(keys) + +// After (1 line) +keys := slices.Sorted(maps.Keys(opts.CustomRules)) +``` + +**Impact:** Low — single occurrence, but a good example of the pattern the rest of the codebase already follows. + +--- + +## 8. C-style `for i := 0; i < n; i++` → `for i := range n` + +**Count:** 6 candidates + +| File | Line | Current | +|---|---|---| +| `pkg/password/generator_test.go` | 84 | `for i := 0; i < len(choices); i++` | +| `pkg/yamlnode/yamlnode.go` | 235, 297 | `for i := 0; i < len(s); i++` — character-by-character parsing | +| `pkg/apphost/eval.go` | 33 | `for i := 0; i < len(src); i++` — character parsing | +| `pkg/output/table.go` | 141 | `for i := 0; i < v.Len(); i++` — reflect.Value iteration | +| `cmd/extensions.go` | 56 | `for i := 0; i < len(namespaceParts)-1; i++` | + +**Recommendation:** Only convert simple iteration patterns. The character-parsing loops in `yamlnode.go` and +`eval.go` modify `i` inside the loop body (e.g., `i++` to skip chars), so they **cannot** use `range n`. + +Convertible: +- `pkg/password/generator_test.go:84` → `for i := range len(choices)` +- `cmd/extensions.go:56` → `for i := range len(namespaceParts)-1` + +Not convertible (loop variable modified inside body): +- `pkg/yamlnode/yamlnode.go:235,297` — skip +- `pkg/apphost/eval.go:33` — skip +- `pkg/output/table.go:141` — uses `reflect.Value.Len()`, fine to convert: `for i := range v.Len()` + +**Impact:** Low — readability improvement for simple cases. + +--- + +## 9. Manual Slice Clone → `slices.Clone` + +**Count:** 82 total matches for `append([]T{}, ...)`, but most are in generated `.pb.go` files or +are actually prepend patterns (building a new slice with a prefix element), not clones. + +**Actual clone candidate (1):** + +| File | Line | Current | +|---|---|---| +| `pkg/project/service_target_external.go` | 298 | `return append([]string{}, endpointsResp.Endpoints...), nil` | + +**Recommendation:** +```go +// Before +return append([]string{}, endpointsResp.Endpoints...), nil + +// After +return slices.Clone(endpointsResp.Endpoints), nil +``` + +Most other `append([]T{}, ...)` patterns are prepend operations (e.g., `append([]string{cmd}, args...)`), +which are correct as-is — `slices.Clone` wouldn't apply. + +**Impact:** Low — single real clone candidate. + +--- + +## 10. `log.Printf` / `log.Println` → Structured Logging + +**Count:** 309 non-test occurrences of `log.Print*` + +**Hotspot files:** + +| File | Count (approx) | +|---|---| +| `pkg/pipeline/pipeline_manager.go` | ~20+ | +| `pkg/tools/pack/pack.go` | ~10 | +| `pkg/tools/github/github.go` | ~10 | +| `pkg/cmdsubst/secretOrRandomPassword.go` | 2 | + +**Recommendation:** This is the **highest-volume** modernization opportunity. Consider adopting `log/slog` +for structured, leveled logging. However, this is a large-effort change that would require: + +1. Defining a project-wide `slog.Handler` configuration +2. Replacing all `log.Printf` calls with `slog.Info`, `slog.Debug`, etc. +3. Ensuring log levels map correctly (many current `log.Printf` calls are debug-level) + +The codebase already has an internal tracing/telemetry system (`internal/tracing/`), so `slog` adoption +should integrate with that. + +**Impact:** High value, but **high effort**. Best done as a dedicated initiative, not incremental cleanup. + +--- + +## 11. Context Patterns + +**Count:** ~90 context usage sites + +The codebase already uses modern context patterns well: +- `context.WithoutCancel` — used in `main.go` and tests (Go 1.21+) ✅ +- `context.WithCancel`, `context.WithTimeout` — standard usage ✅ +- `context.AfterFunc` — not used (Go 1.21+, niche utility) + +**No actionable items** — context usage is modern. + +--- + +## 12. Error Handling Patterns + +**Count:** 2,742 `errors.New` / `fmt.Errorf` occurrences; 12 `errors.Join` usages + +The project already uses `errors.Join` where multi-error aggregation is needed. Error wrapping with +`fmt.Errorf("...: %w", err)` is the standard pattern and is used correctly throughout. + +**No actionable items** — error handling follows Go best practices. + +--- + +## 13. File I/O Patterns + +**Count:** 0 `ioutil.*` usages ✅ + +The codebase has already migrated from `io/ioutil` (deprecated in Go 1.16) to `os.ReadFile`, +`os.WriteFile`, etc. No action needed. + +--- + +## 14. Iterator Patterns (`iter.Seq` / `iter.Seq2`) + +**Count:** 0 direct usages + +The `iter` package (Go 1.23+) is not used directly, though `slices.Collect(maps.Keys(...))` and +range-over-func are used implicitly via the `slices`/`maps` packages. + +**Recommendation:** No immediate action. Iterator patterns are most useful for custom collection types; +the codebase doesn't have strong candidates for custom iterators currently. + +--- + +## 15. JSON Handling + +**Count:** 309 `json.Marshal`/`Unmarshal`/`NewDecoder`/`NewEncoder` usages + +Standard `encoding/json` usage throughout. Go 1.24+ introduced `encoding/json/v2` as an experiment, +but it's not stable yet. + +**No actionable items.** + +--- + +## 16. Crypto/TLS + +**Count:** 13 usages + +TLS configuration in `pkg/httputil/util.go` correctly sets `MinVersion: tls.VersionTLS12`. Standard +crypto usage (`crypto/sha256`, `crypto/rand`) throughout. + +**No actionable items.** + +--- + +## Priority Recommendations + +### Quick Wins (< 1 hour each) + +1. **`math.Min` → built-in `min`** — 1 file, 1 line +2. **`sort.Strings` → `slices.Sort`** — 2 files +3. **Manual keys collect → `slices.Sorted(maps.Keys(...))`** — 1 file +4. **`interface{}` → `any`** via `go fix ./...` — automatic +5. **`slices.Clone` for actual clone** — 1 file + +### Medium Effort (1-4 hours) + +6. **`sort.Slice` → `slices.SortFunc`** — ~8 non-test files +7. **`http.NewRequest` → `http.NewRequestWithContext`** — 6 files +8. **Range-over-int conversions** — 2-3 convertible sites + +### Large Effort (multi-day initiative) + +9. **`sync.Map` → generic typed wrapper** — 8 files, requires design +10. **`log.Printf` → `slog` structured logging** — 309 call sites, requires architecture decision + +--- + +## Already Modern ✅ + +The codebase is **already well-modernized** in several areas: + +- **266 usages** of `slices.*` / `maps.*` packages +- `slices.Sorted(maps.Keys(...))` pattern used in 6+ places +- `maps.Clone`, `maps.Copy` used in pipeline and infra packages +- `slices.Contains`, `slices.ContainsFunc`, `slices.IndexFunc` used extensively +- `slices.DeleteFunc` used for filtering +- `context.WithoutCancel` used correctly +- `errors.Join` used for multi-error scenarios +- Zero `ioutil.*` usages (fully migrated) +- `any` used throughout (only 7 hand-written `interface{}` remain) diff --git a/tmp/go-1.26-modernization-analysis.md b/tmp/go-1.26-modernization-analysis.md new file mode 100644 index 00000000000..ce430dcdd3c --- /dev/null +++ b/tmp/go-1.26-modernization-analysis.md @@ -0,0 +1,623 @@ +# Go 1.26 Modernization Analysis — Azure Developer CLI + +**Date:** 2026-03-12 +**Go Version:** 1.26 (go1.26.1 darwin/arm64) +**Analyzed by:** GPT-5.4 (feature research) + Claude Opus 4.6 (codebase scan) +**Scope:** `cli/azd/` — pkg, cmd, internal + +--- + +## Executive Summary + +The azd codebase is **already well-modernized** — 266 usages of `slices`/`maps` packages, zero `ioutil`, and `errors.Join` in use. However, Go 1.24–1.26 introduced several impactful features the project hasn't adopted yet. This document maps new Go features to concrete azd opportunities, organized by impact and effort. + +### Top 5 Opportunities + +| # | Feature | Impact | Effort | Details | +|---|---------|--------|--------|---------| +| 1 | `errors.AsType[T]` (1.26) | 🟢 High | Low | Generic, type-safe error matching — eliminates boilerplate | +| 2 | `WaitGroup.Go` (1.25) | 🟢 High | Medium | Simplifies concurrent goroutine patterns across CLI | +| 3 | `omitzero` JSON tag (1.24) | 🟡 Medium | Low | Better zero-value omission for `time.Time` and optional fields | +| 4 | `os.Root` (1.24/1.25) | 🟡 Medium | Medium | Secure directory-scoped file access | +| 5 | `testing.T.Context` / `B.Loop` (1.24) | 🟡 Medium | Low | Modern test patterns | + +--- + +## Part 1: New Go 1.24–1.26 Features & azd Applicability + +### 🔤 Language Changes + +#### Generic Type Aliases (Go 1.24) +```go +// Now supported: +type Set[T comparable] = map[T]bool +type Result[T any] = struct { Value T; Err error } +``` +**azd applicability:** Could simplify type layering in `pkg/project/` and `pkg/infra/` where wrapper types are common. Low priority — most types are already well-defined. + +#### `new(expr)` — Initialize Pointer from Expression (Go 1.26) +```go +// Before +v := "hello" +ptr := &v + +// After +ptr := new("hello") +``` +**azd applicability:** Useful for optional pointer fields in protobuf/JSON structs. The codebase has several `to.Ptr()` helper patterns that this could partially replace. Medium value in new code. + +#### Self-Referential Generic Constraints (Go 1.26) +```go +type Adder[A Adder[A]] interface { Add(A) A } +``` +**azd applicability:** Niche — useful for F-bounded polymorphism patterns. No immediate need in azd. + +--- + +### ⚡ Performance Improvements (Free Wins) + +These improvements apply automatically with Go 1.26 — no code changes needed: + +| Feature | Source | Expected Impact | +|---------|--------|-----------------| +| **Green Tea GC** (default in 1.26) | Runtime | 10–40% GC overhead reduction | +| **Swiss Tables map impl** (1.24) | Runtime | 2–3% CPU reduction overall | +| **`io.ReadAll` optimization** (1.26) | stdlib | ~2x faster, lower memory | +| **cgo overhead -30%** (1.26) | Runtime | Faster cgo calls | +| **Stack-allocated slice backing** (1.25/1.26) | Compiler | Fewer heap allocations | +| **Container-aware GOMAXPROCS** (1.25) | Runtime | Better perf in containers | +| **`fmt.Errorf` for plain strings** (1.26) | stdlib | Less allocation, closer to `errors.New` | + +**Action:** Simply building with Go 1.26 gets these for free. Consider benchmarking before/after to quantify improvements for azd's specific workload. + +--- + +### 🛠️ Standard Library Additions to Adopt + +#### 1. `errors.AsType[T]` (Go 1.26) — **HIGH IMPACT** + +Generic, type-safe replacement for `errors.As`: + +```go +// Before (current azd pattern — verbose, requires pre-declaring variable) +var httpErr *azcore.ResponseError +if errors.As(err, &httpErr) { + // use httpErr +} + +// After (Go 1.26 — single expression, type-safe) +if httpErr, ok := errors.AsType[*azcore.ResponseError](err); ok { + // use httpErr +} +``` + +**azd occurrences:** The codebase has ~200+ `errors.As` calls. This is the single highest-impact modernization: +- Eliminates pre-declared error variables +- Type is inferred from the generic parameter +- Faster than `errors.As` in many cases + +**Files with heavy `errors.As` usage:** +- `pkg/azapi/` — Azure API error handling +- `pkg/infra/provisioning/` — deployment error classification +- `cmd/` — command error handling +- `internal/cmd/` — action error handling + +#### 2. `sync.WaitGroup.Go` (Go 1.25) — **HIGH IMPACT** + +```go +// Before (current azd pattern) +var wg sync.WaitGroup +for _, item := range items { + wg.Add(1) + go func() { + defer wg.Done() + process(item) + }() +} +wg.Wait() + +// After (Go 1.25) +var wg sync.WaitGroup +for _, item := range items { + wg.Go(func() { + process(item) + }) +} +wg.Wait() +``` + +**azd occurrences:** Multiple goroutine fan-out patterns across: +- `pkg/account/subscriptions_manager.go` — parallel tenant subscription fetching +- `pkg/project/` — parallel service operations +- `pkg/infra/` — parallel resource operations +- `internal/cmd/` — parallel deployment steps + +**Benefits:** Eliminates `Add(1)` + `defer Done()` boilerplate, reduces goroutine leak risk. + +#### 3. `omitzero` JSON Struct Tag (Go 1.24) — **MEDIUM IMPACT** + +```go +// Before — omitempty doesn't work well with time.Time (zero time is not "empty") +type Config struct { + CreatedAt time.Time `json:"createdAt,omitempty"` // BUG: zero time still serialized +} + +// After — omitzero uses IsZero() method +type Config struct { + CreatedAt time.Time `json:"createdAt,omitzero"` // Correct: omits zero time +} +``` + +**azd applicability:** Any struct with `time.Time` fields or custom types implementing `IsZero()`. Check `pkg/config/`, `pkg/project/`, `pkg/account/` for candidates. + +#### 4. `os.Root` / `os.OpenRoot` (Go 1.24, expanded 1.25) — **MEDIUM IMPACT** + +Secure, directory-scoped filesystem access that prevents path traversal: + +```go +root, err := os.OpenRoot("/app/workspace") +if err != nil { ... } +defer root.Close() + +// All operations are confined to /app/workspace +data, err := root.ReadFile("config.yaml") // OK +data, err := root.ReadFile("../../etc/passwd") // Error: escapes root +``` + +**Go 1.25 additions:** `Chmod`, `Chown`, `MkdirAll`, `ReadFile`, `RemoveAll`, `Rename`, `Symlink`, `WriteFile` + +**azd applicability:** +- `pkg/project/` — reading `azure.yaml` and project files +- `pkg/osutil/` — file utility operations +- Extension framework — sandboxing extension file access +- Any user-supplied path handling for security hardening + +#### 5. `testing.T.Context` / `testing.B.Loop` (Go 1.24) — **MEDIUM IMPACT** + +```go +// T.Context — automatic context tied to test lifecycle +func TestFoo(t *testing.T) { + ctx := t.Context() // cancelled when test ends + result, err := myService.Do(ctx) +} + +// B.Loop — cleaner benchmarks +func BenchmarkFoo(b *testing.B) { + for b.Loop() { // replaces for i := 0; i < b.N; i++ + doWork() + } +} +``` + +**azd applicability:** The test suite has 2000+ test functions. `T.Context()` would simplify context creation in tests that currently use `context.Background()` or `context.TODO()`. + +#### 6. `T.Chdir` (Go 1.24) + +```go +// Before +oldDir, _ := os.Getwd() +os.Chdir(tempDir) +defer os.Chdir(oldDir) + +// After +t.Chdir(tempDir) // auto-restored when test ends +``` + +**azd applicability:** Multiple test files change directory manually. `T.Chdir` is safer and cleaner. + +#### 7. `bytes.Buffer.Peek` (Go 1.26) + +Access buffered bytes without consuming them. Useful in streaming/parsing scenarios. + +#### 8. `testing/synctest` (Go 1.25 GA) + +Fake-time testing for concurrent code: + +```go +synctest.Run(func() { + go func() { + time.Sleep(time.Hour) // instant in fake time + ch <- result + }() + synctest.Wait() // wait for all goroutines in bubble +}) +``` + +**azd applicability:** Could simplify testing of timeout/retry logic in `pkg/retry/`, `pkg/httputil/`, and polling operations. + +#### 9. String/Bytes Iterator Helpers (Go 1.24) + +```go +// New iterator-returning functions +for line := range strings.Lines(text) { ... } +for part := range strings.SplitSeq(text, ",") { ... } +for field := range strings.FieldsSeq(text) { ... } +``` + +**azd applicability:** Can replace `strings.Split` + loop patterns where only iteration is needed (avoids allocating the intermediate slice). + +#### 10. `testing.T.Attr` / `T.ArtifactDir` (Go 1.25/1.26) + +Structured test metadata and artifact directories for richer test output. + +--- + +### 🔧 Tooling Changes to Leverage + +#### `go fix` Modernizers (Go 1.26) — **Use Now** + +The rewritten `go fix` tool includes automatic modernizers: +```bash +go fix ./... +``` +This will automatically: +- Convert `interface{}` → `any` +- Simplify loop patterns +- Apply other Go version-appropriate modernizations + +**Recommendation:** Run `go fix ./...` as a first pass before any manual changes. + +#### `tool` Directives in `go.mod` (Go 1.24) + +``` +// go.mod +tool ( + golang.org/x/tools/cmd/stringer + github.com/golangci/golangci-lint/cmd/golangci-lint +) +``` + +Replaces `tools.go` pattern for declaring tool dependencies. Pinned versions, cached execution. + +**azd applicability:** The project likely uses build tools that could be declared here. + +#### `go.mod` `ignore` Directive (Go 1.25) + +Ignore directories that shouldn't be part of the module: +``` +ignore vendor/legacy +``` + +**azd applicability:** Could be useful for the extensions directory structure. + +#### `//go:fix inline` (Go 1.26) + +Mark functions for source-level inlining by `go fix`: +```go +//go:fix inline +func deprecated() { newFunction() } +``` + +**azd applicability:** Useful when deprecating internal helper functions — `go fix` will inline callers automatically. + +--- + +## Part 2: Codebase Scan — Specific Modernization Opportunities + +### Quick Wins (< 1 hour total) + +#### A. `math.Min` → built-in `min()` (1 site) + +| File | Line | +|------|------| +| `pkg/account/subscriptions_manager.go` | 304 | + +```go +// Before +numWorkers := int(math.Min(float64(len(tenants)), float64(maxWorkers))) +// After +numWorkers := min(len(tenants), maxWorkers) +``` + +#### B. `sort.Strings` → `slices.Sort` (2 files) + +| File | Line | +|------|------| +| `internal/agent/tools/io/file_search.go` | 146 | +| `extensions/azure.ai.finetune/internal/cmd/validation.go` | 75 | + +#### C. Manual keys collect → `slices.Sorted(maps.Keys(...))` (1 file) + +| File | Line | +|------|------| +| `pkg/azdext/scope_detector.go` | 116-120 | + +```go +// Before (5 lines) +keys := make([]string, 0, len(opts.CustomRules)) +for k := range opts.CustomRules { keys = append(keys, k) } +slices.Sort(keys) +// After (1 line) +keys := slices.Sorted(maps.Keys(opts.CustomRules)) +``` + +#### D. Manual slice clone → `slices.Clone` (1 file) + +| File | Line | +|------|------| +| `pkg/project/service_target_external.go` | 298 | + +```go +// Before +return append([]string{}, endpointsResp.Endpoints...), nil +// After +return slices.Clone(endpointsResp.Endpoints), nil +``` + +#### E. Run `go fix ./...` (automatic) + +Converts remaining `interface{}` → `any` (7 hand-written occurrences) and applies other modernizations. + +--- + +### Medium Effort (1–4 hours each) + +#### F. `sort.Slice` → `slices.SortFunc` (~8 non-test files) + +| File | Pattern | +|------|---------| +| `pkg/infra/provisioning_progress_display.go:182` | `sort.Slice(newlyDeployedResources, ...)` | +| `pkg/account/subscriptions_manager.go:329` | `sort.Slice(allSubscriptions, ...)` | +| `pkg/account/subscriptions.go:84,139,168` | `sort.Slice(...)` | +| + 3 more files | — | + +```go +// Before +sort.Slice(items, func(i, j int) bool { return items[i].Name < items[j].Name }) +// After +slices.SortFunc(items, func(a, b Item) int { return cmp.Compare(a.Name, b.Name) }) +``` + +#### G. `http.NewRequest` → `http.NewRequestWithContext` (6 files) + +| File | +|------| +| `tools/avmres/main.go:193` | +| `internal/telemetry/appinsights-exporter/transmitter.go:80` | +| `extensions/azure.ai.agents/internal/project/parser.go:1041,1106,1161` | +| `pkg/llm/github_copilot.go:388` | + +#### H. Range-over-int conversions (2-3 sites) + +| File | Convertible? | +|------|-------------| +| `pkg/password/generator_test.go:84` | ✅ Yes | +| `cmd/extensions.go:56` | ✅ Yes | +| `pkg/output/table.go:141` | ✅ Yes (reflect) | +| `pkg/yamlnode/yamlnode.go:235,297` | ❌ No (modifies i) | +| `pkg/apphost/eval.go:33` | ❌ No (modifies i) | + +--- + +### Large Initiatives (Multi-day) + +#### I. `sync.Map` → Generic Typed Wrapper (13 usages, 8 files) + +Create a `SyncMap[K, V]` utility type to eliminate type assertions: + +```go +type SyncMap[K comparable, V any] struct { m sync.Map } + +func (s *SyncMap[K, V]) Load(key K) (V, bool) { ... } +func (s *SyncMap[K, V]) Store(key K, value V) { ... } +func (s *SyncMap[K, V]) Delete(key K) { ... } +func (s *SyncMap[K, V]) Range(f func(K, V) bool) { ... } +``` + +**Files to update:** +- `internal/cmd/add/add_select_ai.go` +- `pkg/grpcbroker/message_broker.go` +- `pkg/auth/credential_providers.go` +- `pkg/devcentersdk/developer_client.go` +- `pkg/ux/canvas.go` +- `pkg/ai/model_service.go` +- `pkg/containerapps/container_app.go` + +#### J. `log.Printf` → `slog` Structured Logging (309 sites) + +This is the **highest-volume opportunity** but requires an architecture decision: +- Define project-wide `slog.Handler` configuration +- Integrate with existing `internal/tracing/` system +- Map current `log.Printf` calls to appropriate `slog.Info`/`slog.Debug` levels +- Consider `--debug` flag integration + +**Recommendation:** Treat as a separate initiative with its own design doc. + +#### K. `errors.AsType` Migration (200+ sites) + +Systematic migration of `errors.As` → `errors.AsType[T]` across the codebase. + +**Recommendation:** Can be done incrementally — start with the highest-traffic error handling paths in `pkg/azapi/` and `pkg/infra/`. + +--- + +## Part 3: Testing Modernization + +### Current State +- **2000+ test functions** across the codebase +- Uses `testify/mock` for mocking +- Table-driven tests are the standard pattern + +### Opportunities + +| Feature | Where to Apply | Impact | +|---------|---------------|--------| +| `T.Context()` | Tests using `context.Background()` | Cleaner test lifecycle | +| `T.Chdir()` | Tests with manual `os.Chdir` | Safer directory changes | +| `B.Loop()` | Benchmarks using `for i := 0; i < b.N; i++` | Cleaner benchmarks | +| `testing/synctest` | Timeout/retry/polling tests | Deterministic timing | +| `T.ArtifactDir()` | Tests generating output files | Organized test artifacts | +| `testing/cryptotest.SetGlobalRandom` | Crypto tests needing determinism | Reproducible crypto tests | + +--- + +## Part 4: Security Improvements + +| Feature | Description | azd Relevance | +|---------|-------------|---------------| +| `os.Root` (1.24/1.25) | Path-traversal-safe file access | Project file reading, extension sandbox | +| `net/http.CrossOriginProtection` (1.25) | Built-in CSRF via Fetch Metadata | Local dev server callbacks | +| Post-quantum TLS (1.26) | `SecP256r1MLKEM768`, `SecP384r1MLKEM1024` | Automatic via Go runtime | +| RSA min 1024-bit (1.24) | Enforced by crypto/rsa | Automatic | +| Heap base randomization (1.26) | ASLR improvement | Automatic | + +--- + +## Part 5: PR-Sized Adoption Roadmap + +Each item below is scoped to a single, small PR that can be reviewed independently. + +--- + +### PR 1: `go fix` automated modernizations +**Files:** All `.go` files (auto-applied) +**Changes:** `interface{}` → `any`, loop simplifications, other `go fix` modernizers +**Review notes:** Pure mechanical transform — reviewer just confirms no behavioral changes +**Commands:** +```bash +go fix ./... +gofmt -s -w . +``` + +--- + +### PR 2: Built-in `min()` and `slices.Sort` quick wins +**Files:** 3 files, ~5 lines changed +**Changes:** +- `pkg/account/subscriptions_manager.go`: `math.Min(float64, float64)` → `min()` +- `internal/agent/tools/io/file_search.go`: `sort.Strings` → `slices.Sort` +- `extensions/azure.ai.finetune/internal/cmd/validation.go`: `sort.Strings` → `slices.Sort` + +--- + +### PR 3: `slices.Clone` and `slices.Sorted(maps.Keys(...))` one-liners +**Files:** 2 files, ~6 lines changed +**Changes:** +- `pkg/project/service_target_external.go`: `append([]T{}, s...)` → `slices.Clone(s)` +- `pkg/azdext/scope_detector.go`: manual keys collect + sort → `slices.Sorted(maps.Keys(...))` + +--- + +### PR 4: `sort.Slice` → `slices.SortFunc` in account package +**Files:** 2 files (~4 call sites) +**Changes:** +- `pkg/account/subscriptions_manager.go`: `sort.Slice` → `slices.SortFunc` with `cmp.Compare` +- `pkg/account/subscriptions.go`: 3 × `sort.Slice` → `slices.SortFunc` + +--- + +### PR 5: `sort.Slice` → `slices.SortFunc` in remaining packages +**Files:** ~4 files +**Changes:** +- `pkg/infra/provisioning_progress_display.go` +- Other non-test files with `sort.Slice` +- Does **not** touch `sort.Sort` on `sort.Interface` types (leave those as-is) + +--- + +### PR 6: `http.NewRequest` → `http.NewRequestWithContext` +**Files:** 6 files +**Changes:** +- `tools/avmres/main.go` +- `internal/telemetry/appinsights-exporter/transmitter.go` +- `extensions/azure.ai.agents/internal/project/parser.go` (3 sites) +- `pkg/llm/github_copilot.go` +- Thread existing `ctx` or use `context.Background()` explicitly + +--- + +### PR 7: Range-over-int modernization +**Files:** 3 files, ~3 lines each +**Changes:** +- `pkg/password/generator_test.go`: `for i := 0; i < len(choices); i++` → `for i := range len(choices)` +- `cmd/extensions.go`: similar +- `pkg/output/table.go`: `for i := 0; i < v.Len(); i++` → `for i := range v.Len()` +- Skips parser loops where `i` is modified inside the body + +--- + +### PR 8: Generic `SyncMap[K, V]` utility type +**Files:** 1 new file + 8 files updated +**Changes:** +- Create `pkg/sync/syncmap.go` with `SyncMap[K, V]` generic wrapper +- Update 8 files to use typed map instead of `sync.Map` + type assertions + +--- + +### PR 9: Adopt `errors.AsType[T]` — Azure API error paths +**Files:** `pkg/azapi/` (focused scope) +**Changes:** +- Replace `var errT *T; errors.As(err, &errT)` → `errT, ok := errors.AsType[*T](err)` +- Start with the highest-traffic error handling paths + +--- + +### PR 10: Adopt `errors.AsType[T]` — infra/provisioning +**Files:** `pkg/infra/provisioning/` and `pkg/infra/` +**Changes:** Same pattern as PR 9, scoped to infrastructure error handling + +--- + +### PR 11: Adopt `errors.AsType[T]` — commands and actions +**Files:** `cmd/` and `internal/cmd/` +**Changes:** Same pattern, scoped to command-level error handling + +--- + +### PR 12: Adopt `WaitGroup.Go` in concurrent patterns +**Files:** 4-6 files with goroutine fan-out +**Changes:** +- `pkg/account/subscriptions_manager.go`: parallel tenant fetching +- `pkg/project/`: parallel service operations +- Other files with `wg.Add(1); go func() { defer wg.Done(); ... }()` pattern +- Replace with `wg.Go(func() { ... })` + +--- + +### PR 13: Test modernization — `T.Context()` adoption (batch 1) +**Files:** ~20 test files (scoped to one package, e.g., `pkg/project/`) +**Changes:** +- Replace `context.Background()` / `context.TODO()` → `t.Context()` in tests +- One package at a time to keep PRs reviewable + +--- + +### PR 14: Test modernization — `T.Chdir()` adoption +**Files:** Test files with manual `os.Chdir` + defer restore +**Changes:** Replace with `t.Chdir(dir)` — auto-restored when test ends + +--- + +### Future PRs (require design discussion first) + +| PR | Topic | Notes | +|----|-------|-------| +| 15+ | `omitzero` JSON tags | Audit all JSON structs with `time.Time` fields | +| 16+ | `os.Root` secure file access | Needs design doc for project file reading | +| 17+ | `slog` structured logging | 309 sites — needs architecture decision | +| 18+ | `testing/synctest` for retry/timeout tests | Evaluate which tests benefit | +| 19+ | `tool` directives in `go.mod` | Replace `tools.go` pattern | + +--- + +## Appendix A: Already Modern ✅ + +The codebase already excels in these areas: +- **266 usages** of `slices.*` / `maps.*` packages +- `slices.Sorted(maps.Keys(...))` pattern in 6+ places +- `maps.Clone`, `maps.Copy` used correctly +- `context.WithoutCancel` used where appropriate +- `errors.Join` for multi-error aggregation +- Zero `ioutil.*` — fully migrated +- `any` used throughout (only 7 hand-written `interface{}` remain) + +## Appendix B: No Action Needed + +| Area | Status | +|------|--------| +| `ioutil` migration | ✅ Complete | +| `context.WithoutCancel` | ✅ Already used | +| `errors.Join` | ✅ Already used (12 sites) | +| `slices.Contains` | ✅ Used extensively | +| Crypto/TLS config | ✅ Modern (TLS 1.2 minimum) | +| File I/O patterns | ✅ Uses `os.ReadFile`/`os.WriteFile` | diff --git a/tmp/reports/azd-telemetry-28d-report.md b/tmp/reports/azd-telemetry-28d-report.md new file mode 100644 index 00000000000..1eceb672e7c --- /dev/null +++ b/tmp/reports/azd-telemetry-28d-report.md @@ -0,0 +1,342 @@ +# Azure Developer CLI (azd) — Rolling 28-Day Telemetry Report + +**Period:** Feb 21 – Mar 18, 2026 +**Source:** `ddazureclients.kusto.windows.net` / `DevCli` database +**Generated:** 2026-03-21 + +--- + +## Executive Summary + +| Metric | Value | +|--------|-------| +| **Total Executions** | 10,178,008 | +| **Unique Users** (DevDeviceId) | 129,003 | +| **Overall Success Rate** | 64.5% | +| **Unique Templates Used** | 2,533 | +| **Unique Azure Subscriptions** | 9,183 | +| **Unique Customer Orgs** | 3,857+ | + +### Monthly KPI Comparison (from AzdKPIs table) + +| KPI | Jan 2026 | Feb 2026 | Δ | +|-----|----------|----------|---| +| MAU (Active1d) | 16,636 | 18,811 | **+13.1%** | +| Engaged (2d) | 3,950 | 4,072 | +3.1% | +| Dedicated (10d) | 369 | 364 | -1.4% | +| Provisions | 59,152 | 63,881 | +8.0% | +| Deployments | 54,958 | 56,728 | +3.2% | +| Successful Provisions | 28,835 | 30,742 | +6.6% | +| Successful Deployments | 41,058 | 43,186 | +5.2% | +| Azure Subscriptions | 6,420 | 7,339 | **+14.3%** | + +--- + +## Daily Active Users Trend + +| Date | DAU | Executions | +|------|-----|-----------| +| Feb 21 (Fri) | 504 | 26,306 | +| Feb 22 (Sat) | 3,131 | 142,943 | +| Feb 23 (Sun) | 9,505 | 434,029 | +| Feb 24 (Mon) | 11,499 | 575,189 | +| Feb 25 (Tue) | 11,570 | 622,173 | +| Feb 26 (Wed) | 10,742 | 481,576 | +| Feb 27 (Thu) | 9,665 | 397,341 | +| Feb 28 (Fri) | 3,566 | 223,880 | +| Mar 1 (Sat) | 3,244 | 254,988 | +| Mar 2 (Sun) | 10,093 | 491,753 | +| Mar 3 (Mon) | 10,522 | 458,583 | +| Mar 4 (Tue) | 10,458 | 580,816 | +| Mar 5 (Wed) | 10,359 | 518,339 | +| Mar 6 (Thu) | 9,489 | 500,349 | +| Mar 7 (Fri) | 3,506 | 247,362 | +| Mar 8 (Sat) | 3,267 | 168,655 | +| Mar 9 (Sun) | 10,356 | 563,312 | +| Mar 10 (Mon) | 11,275 | 519,414 | +| Mar 11 (Tue) | 11,004 | 382,957 | +| Mar 12 (Wed) | 10,802 | 425,650 | +| Mar 13 (Thu) | 10,106 | 311,655 | +| Mar 14 (Fri) | 3,586 | 348,198 | +| Mar 15 (Sat) | 3,397 | 162,191 | +| Mar 16 (Sun) | 10,616 | 333,201 | +| Mar 17 (Mon) | 11,241 | 558,356 | +| Mar 18 (Tue) | 11,699 | 448,755 | + +> Weekday DAU averages ~10,500–11,500 users. Weekend dips to ~3,200–3,600. Pattern is healthy and consistent. + +--- + +## Top Commands (by executions) + +| Command | Executions | Unique Users | Success % | +|---------|-----------|-------------|-----------| +| `azd auth token` | 8,237,762 | 20,692 | 58.9% | +| `azd env set` | 428,612 | 33,758 | 99.5% | +| `azd auth login` | 208,951 | 72,188 | 88.3% | +| `azd env get-values` | 190,212 | 24,792 | 98.9% | +| `azd env get-value` | 173,396 | 10,748 | 84.3% | +| `azd env list` | 153,117 | 18,883 | 99.1% | +| **`azd provision`** | **127,640** | **40,469** | **58.8%** | +| **`azd deploy`** | **115,974** | **43,735** | **80.8%** | +| `azd package` | 76,273 | 20,476 | 92.8% | +| **`azd up`** | **71,276** | **11,110** | **31.0%** ⚠️ | +| `azd config set` | 62,626 | 40,166 | 100% | +| `azd env new` | 47,364 | 29,470 | 93.5% | +| `azd init` | 46,133 | 6,251 | 91.0% | +| `azd env select` | 39,631 | 26,229 | 84.2% | + +--- + +## Top Templates (by unique users) + +| Template | Users | Executions | +|----------|-------|-----------| +| **azd-init** (interactive init) | 8,427 | 81,078 | +| **azure-search-openai-demo** | 2,542 | 259,785 | +| **chat-with-your-data-solution-accelerator** | 625 | 5,707 | +| **multi-agent-custom-automation-engine** | 550 | 19,742 | +| **todo-nodejs-mongo** | 469 | 1,925 | +| **agentic-applications-for-unified-data-foundation** | 461 | 12,653 | + +--- + +## Execution Environments — IDEs & AI Agents + +### All Execution Environments + +| Environment | Executions | Unique Users | User Share | +|-------------|-----------|-------------|------------| +| **Desktop** (terminal) | 8,699,096 | 39,274 | 30.4% | +| **GitHub Actions** | 421,968 | 37,759 | 29.2% | +| **Azure Pipelines** | 358,116 | 32,543 | 25.2% | +| **VS Code** (extension) | 93,024 | 16,006 | 12.4% | +| **Visual Studio** | 76,789 | 4,886 | 3.8% | +| **Azure CloudShell** | 19,456 | 3,686 | 2.9% | +| **Claude Code** 🔥 | 367,193 | 699 | 0.5% | +| **GitHub Codespaces** | 36,097 | 1,066 | 0.8% | +| **GitLab CI** | 36,519 | 1,142 | 0.9% | +| **GitHub Copilot CLI** | 50,115 | 579 | 0.4% | +| **OpenCode** | 3,204 | 38 | — | +| **Gemini** | 102 | 12 | — | + +### 🤖 AI/LLM Agent Deep Dive + +#### Weekly Trend + +| Week | Claude Code Users | Claude Code Exec | Copilot CLI Users | Copilot CLI Exec | OpenCode Users | Gemini Users | +|------|------------------|-----------------|-------------------|-----------------|---------------|-------------| +| Feb 16 | 20 | 1,213 | 9 | 177 | 3 | — | +| Feb 23 | 202 | 28,918 | 185 | 12,362 | 17 | 6 | +| Mar 2 | 263 | 133,593 | 228 | 18,716 | 11 | 2 | +| Mar 9 | **263** | **159,470** | 218 | 11,015 | 14 | 2 | +| Mar 16 | 218 | 43,999 | 173 | 7,845 | 12 | 2 | + +#### Success Rates Over Time + +| Week | Claude Code | Copilot CLI | OpenCode | Gemini | +|------|------------|------------|----------|--------| +| Feb 16 | 23.1% | 79.1% | 75.0% | — | +| Feb 23 | 31.2% | 74.4% | 71.2% | 62.0% | +| Mar 2 | 46.4% | 48.5% | 24.5% | 69.2% | +| Mar 9 | **87.9%** | 69.5% | 66.0% | 80.0% | +| Mar 16 | 82.5% | 63.1% | 74.8% | 20.0% | + +#### Top Commands by AI Agent + +**Claude Code** (367K executions, 699 users): + +| Command | Executions | Users | +|---------|-----------|-------| +| `auth token` | 284,257 | 375 | +| `env list` | 63,530 | 171 | +| `env get-values` | 5,804 | 217 | +| `env set` | 3,859 | 186 | +| `deploy` | 2,920 | 192 | +| `provision` | 1,198 | 170 | +| `up` | 709 | 125 | + +**GitHub Copilot CLI** (50K executions, 579 users): + +| Command | Executions | Users | +|---------|-----------|-------| +| `auth token` | 18,303 | 319 | +| `env get-value` | 6,747 | 69 | +| `env set` | 4,140 | 203 | +| `env get-values` | 3,766 | 214 | +| `deploy` | 3,043 | 187 | +| `provision` | 2,957 | 208 | +| `up` | 1,903 | 172 | + +> Notable: Copilot CLI is also running **MCP operations** (`mcp.validate_azure_yaml`, `mcp.iac_generation_rules`, `mcp.infrastructure_generation`, etc.) — 70 total MCP calls from 19+ users. + +#### AI Agent Key Insights + +- 🔥 **Claude Code is exploding** — went from 20 users to 263 in 4 weeks, with 367K executions (more than Copilot CLI despite fewer users). Heavy `auth token` loop suggests agentic workflow patterns. +- **Claude Code success rate improved dramatically** — from 23% to 88% in 4 weeks, suggesting rapid integration maturation. +- **Copilot CLI** is steady at ~200 users/week with 50K executions. Already using azd's MCP tools. +- **AI agents collectively**: ~1,300 users, 420K executions — about **4% of total azd volume** and growing fast. + +--- + +## Customer Breakdown + +### By Segment + +| Segment | Customers | Users | Subscriptions | Executions | +|---------|-----------|-------|---------------|------------| +| **SMB Commercial** | 1,852 | 26,637 | 2,732 | 473K | +| **Unspecified** | 993 | 21,622 | 2,466 | 445K | +| **Upper Majors Commercial** | 336 | 11,325 | 588 | 83K | +| **Strategic Commercial** | 188 | 5,054 | 564 | 113K | +| **Corporate Commercial** | 234 | 3,370 | 822 | 41K | +| **Upper Majors Public Sector** | 118 | 2,434 | 175 | 19K | +| Majors Growth Commercial | 34 | 418 | 44 | 5K | +| Strategic Public Sector | 29 | 264 | 49 | 5K | + +### Top Customers (by unique users) + +| Customer | Segment | Country | Users | Subs | Executions | Success% | +|----------|---------|---------|-------|------|------------|----------| +| **CoreAI - Platform & Tools** | Internal | 🇺🇸 US | 9,246 | 35 | 73K | 86.9% | +| **Investec Bank** | Upper Majors | 🇬🇧 UK | 6,471 | 74 | 15K | 90.3% | +| **Microsoft** | Internal | 🇺🇸 US | 3,976 | 978 | 149K | 82.8% | +| **Cloud + AI** | Internal | 🇨🇳 CN | 3,066 | 183 | 107K | 96.3% | +| **Vee Friends** | SMB | 🇺🇸 US | 2,585 | 1 | 8K | 99.0% | +| **Grupo Cosan** | Upper Majors | 🇧🇷 BR | 1,667 | 1 | 5K | 100% | +| **Puerto Rico PRITS** | Public Sector | 🇵🇷 PR | 1,422 | 2 | 4K | 94.9% | +| **H&M** | Strategic | 🇸🇪 SE | 1,107 | 40 | 8K | 91.7% | +| **Jersey Telecoms** | Corporate | 🇬🇧 UK | 951 | 2 | 7K | 99.6% | +| **Volkswagen** | Strategic | 🇩🇪 DE | 664 | 17 | 39K | 98.6% | +| **Microsoft Security** | Internal | 🇺🇸 US | 640 | 15 | 14K | 95.7% | +| **IBM** | Strategic | 🇺🇸 US | 385 | 7 | 4K | 83.2% | +| **Deloitte** | Strategic | 🇺🇸 US | 329 | 10 | 1K | 96.3% | +| **bp** | Strategic | 🇬🇧 UK | 271 | 9 | 6K | 97.9% | +| **Allianz Technology** | Strategic | 🇩🇪 DE | 233 | 8 | 11K | 99.8% | +| **Rabobank** | Strategic | 🇳🇱 NL | 248 | 8 | 462 | 92.2% | +| **EY Global** | Strategic | 🇺🇸 US | 201 | 25 | 2K | 71.6% | +| **Mercedes-Benz** | Strategic | 🇩🇪 DE | 195 | 6 | 3K | 99.0% | + +--- + +## Error Analysis + +### Overall Failure Rate Trend + +| Week | Total Exec | Failures | Fail Rate | +|------|-----------|----------|-----------| +| Feb 16 | 168K | 87K | 52.0% | +| Feb 23 | 2,989K | 1,050K | 35.1% | +| Mar 2 | 2,966K | 1,015K | 34.2% | +| Mar 9 | 2,713K | 948K | 35.0% | +| Mar 16 | 1,340K | 513K | 38.3% | + +### Success Rates by Command + +| Command | Total | Failures | Success% | Users | +|---------|-------|----------|----------|-------| +| `auth token` | 8,236,682 | 3,384,478 | **58.9%** | 20,689 | +| `auth login` | 208,930 | 24,476 | 88.3% | 72,184 | +| `provision` | 127,630 | 52,639 | **58.8%** | 40,466 | +| `deploy` | 115,961 | 22,243 | 80.8% | 43,735 | +| `package` | 76,268 | 5,520 | 92.8% | 20,476 | +| **`up`** | **71,271** | **49,154** | **31.0%** ⚠️ | 11,110 | +| `env new` | 47,355 | 3,098 | 93.5% | 29,463 | +| `init` | 46,132 | 4,171 | 91.0% | 6,251 | +| `down` | 15,830 | 2,761 | 82.6% | 2,982 | +| `restore` | 4,979 | 1,993 | **60.0%** | 693 | + +### Top Result Codes (failure reasons) + +| Result Code | Count | Users | Category | +|-------------|-------|-------|----------| +| `UnknownError` | 2,383,049 | 8,128 | Uncategorized | +| `auth.login_required` | 547,007 | 1,984 | Auth | +| `internal.errors_errorString` | 462,344 | 16,978 | Internal | +| `auth.not_logged_in` | 66,331 | 1,769 | Auth | +| `error.suggestion` | 33,762 | 7,475 | User guidance | +| `service.arm.deployment.failed` | 22,033 | 4,025 | ARM | +| `service.aad.failed` | 13,619 | 1,276 | AAD | +| `internal.azidentity_AuthenticationFailedError` | 8,732 | 196 | Auth (identity) | +| `service.arm.validate.failed` | 6,426 | 1,220 | ARM | +| `service.arm.400` | 6,371 | 1,479 | ARM | +| `tool.bicep.failed` | 5,099 | 1,437 | Bicep | +| `tool.pwsh.failed` | 4,931 | 688 | PowerShell | +| `tool.docker.failed` | 4,655 | 1,024 | Docker | +| `tool.dotnet.failed` | 3,732 | 1,345 | .NET | +| `user.canceled` | 3,630 | 1,739 | User action | +| `tool.Docker.missing` | 2,936 | 784 | Docker not installed | +| `tool.terraform.failed` | 2,872 | 355 | Terraform | + +### Provision / Deploy / Up Error Breakdown + +| Command | Error Category | Result Code | Count | Users | +|---------|---------------|-------------|-------|-------| +| `provision` | ARM | arm.deployment.failed | 12,395 | 3,908 | +| `up` | ARM | error.suggestion | 7,036 | 1,928 | +| `up` | ARM | arm.deployment.failed | 4,725 | 1,466 | +| `provision` | ARM | arm.validate.failed | 3,393 | 1,208 | +| `provision` | ARM | arm.400 | 3,130 | 1,143 | +| `provision` | Bicep | bicep.failed | 2,715 | 1,311 | +| `up` | ARM | arm.400 | 2,540 | 940 | +| `provision` | PowerShell | pwsh.failed | 2,053 | 560 | +| `provision` | Terraform | terraform.failed | 1,873 | 335 | +| `deploy` | Docker | docker.failed | 1,652 | 692 | +| `up` | Docker | Docker.missing | 1,028 | 540 | +| `deploy` | .NET | dotnet.failed | 1,304 | 559 | + +### 🔍 `cmd.auth.token` Error Deep Dive + +`auth token` accounts for **3.38M of 3.61M total failures (94%)**. Breakdown: + +| Result Code | Error Category | Count | Users | % of Auth Token Failures | +|-------------|---------------|-------|-------|--------------------------| +| `UnknownError` | unknown | 2,352,993 | 6,403 | **69.5%** | +| `auth.login_required` | unknown | 543,972 | 1,353 | **16.1%** | +| `internal.errors_errorString` | unknown | 398,164 | 5,285 | **11.8%** | +| `auth.not_logged_in` | unknown | 65,643 | 1,233 | 1.9% | +| `service.aad.failed` | aad | 12,246 | 658 | 0.4% | +| `internal.azidentity_AuthenticationFailedError` | unknown | 7,258 | 65 | 0.2% | + +> ⚠️ **Classification gap:** 99.6% of auth token failures are categorized as "unknown" even when result codes like `auth.login_required` and `auth.not_logged_in` provide clear signals. Only `service.aad.failed` (12K) gets properly categorized. + +#### Auth Token Failure Rate by Execution Environment + +| Environment | Total | Failures | Fail % | +|-------------|-------|----------|--------| +| **Desktop** | 7,888,323 | 3,250,684 | **41.2%** | +| **Claude Code** | 284,257 | 116,750 | **41.1%** | +| **GitHub Copilot CLI** | 18,302 | 11,156 | **61.0%** ⚠️ | +| **GitHub Actions** | 12,915 | 383 | 3.0% ✅ | +| **GitHub Codespaces** | 10,580 | 1,417 | 13.4% | +| **Azure Pipelines** | 9,692 | 2,219 | 22.9% | +| **GitLab CI** | 5,147 | 0 | 0% ✅ | +| **OpenCode** | 2,053 | 908 | 44.2% | + +> CI/CD environments have near-zero auth token failures (service principal auth). Interactive/agent environments hit ~41-61% failure rates due to token expiry and "not logged in" retries. + +--- + +## Key Takeaways & Recommendations + +### 📈 Growth Signals +1. **MAU up 13% MoM**, Azure subscriptions up **14%** — strong growth trajectory +2. **AI agent adoption is hockey-sticking** — Claude Code went from 20→263 users in 4 weeks +3. **3,857+ customer organizations** actively using azd, with marquee enterprise logos (VW, H&M, bp, Mercedes, Allianz, IBM) + +### ⚠️ Areas of Concern +1. **`azd up` at 31% success rate** — the flagship command. Compounds provision + deploy failures. 49K failures affecting 11K users. +2. **`auth token` dominates failure volume** — 3.38M failures, but most are expected retry patterns (token refresh). Inflates overall failure rate from ~6% (excluding auth token) to ~35%. +3. **Error classification is broken** — 2.35M errors classified as `UnknownError` with no detail. `auth.login_required` (544K) is categorized as "unknown" despite having a clear result code. + +### 🔧 Suggested Actions +1. **Fix error categorization** — Map `auth.login_required`, `auth.not_logged_in`, and `azidentity_AuthenticationFailedError` result codes to an `auth` error category instead of `unknown`. +2. **Investigate `UnknownError`** (2.35M) — Add better error introspection to surface what's actually failing. +3. **Improve `azd up` reliability** — At 31% success, the compound command needs better pre-flight checks, clearer error messages, and possibly staged rollback. +4. **Address Docker-not-installed** (2,936 failures, 784 users) — Better pre-req detection and user guidance before attempting container deployments. +5. **Optimize for AI agents** — Claude Code and Copilot CLI are growing fast. Consider agent-specific auth flows (non-interactive token acquisition) and reducing the `auth token` retry loop noise. +6. **Monitor EY Global** — 71.6% success rate for a Strategic account is below the 90%+ benchmark seen at other enterprise customers. + +--- + +*Report generated from `AzdOperations`, `AzdKPIs`, and `Templates` tables on the `ddazureclients` Kusto cluster.* diff --git a/tmp/reports/azd-telemetry-opportunities-report.md b/tmp/reports/azd-telemetry-opportunities-report.md new file mode 100644 index 00000000000..6ca6f121e49 --- /dev/null +++ b/tmp/reports/azd-telemetry-opportunities-report.md @@ -0,0 +1,231 @@ +# azd Telemetry: Improvement Opportunities Report + +**Period:** Rolling 28 days (Feb 21 – Mar 18, 2026) +**Generated:** 2026-03-22 (updated 2026-03-23) +**Source:** Telemetry report + GitHub issue/PR cross-reference + +--- + +## Executive Summary + +azd processes **10.2M executions/28d** with a **64.5% overall success rate**. However, this headline number is misleading — `auth token` alone accounts for **3.38M of 3.61M failures (94%)**. Excluding auth token, the real command failure rate is closer to **~6%**. + +The three largest improvement opportunities are: + +| Opportunity | Volume Impact | Status | +|---|---|---| +| 1. **UnknownError bucket** | 2.35M errors with zero classification | 🟢 PR #7241 (in review) | +| 2. **Auth error misclassification** | 610K errors in wrong category | 🟢 PR #7235 (all CI green) | +| 3. **`azd up` at 31% success** | 49K failures, 11K users | 🟢 PR #7236 (auth pre-flight, all CI green) | + +--- + +## Opportunity 1: UnknownError — The Biggest Blind Spot + +### Scale +- **2,352,993 errors/28d** classified as `UnknownError` +- **6,403 users** affected +- **69.5%** of all `auth token` failures +- Currently provides **zero diagnostic signal** + +### Root Cause (confirmed) +`EndWithStatus(err)` in several high-volume code paths bypasses `MapError()` — the central error classification function. The telemetry converter then falls back to `"UnknownFailure"` when the span status description is empty or unrecognized. + +### Fix: PR #7241 (in review) +- MCP tool handler and Copilot agent spans now route through `MapError()` +- `EndWithStatus` fallback prefixed with `internal.` for consistency + +### Existing Work +| Issue/PR | Status | Coverage | +|---|---|---| +| **#7239** / PR #7241 | 🟢 PR open, CI green | Routes spans through MapError | +| #6796 "Reduce Unknown error bucket" | ✅ Closed (partial fix) | Addressed some categories | +| #6576 "Follow up on unknown errors classification" | ✅ Closed | Follow-up from #6796 | +| #5743 "Better unknown error breakdowns" | ✅ Closed | Added `errorType()` introspection | + +--- + +## Opportunity 2: Auth Error Classification ✅ IN PROGRESS + +### Scale +- **610K errors/28d** miscategorized as `"unknown"` instead of `"aad"` +- **~17%** of all auth token errors + +### Fix: PR #7235 (all CI green, awaiting review) +| Error | Before | After | +|---|---|---| +| `auth.login_required` (544K) | unknown ❌ | aad ✅ | +| `auth.not_logged_in` (66K) | unknown ❌ | aad ✅ | +| `azidentity.AuthenticationFailedError` (8.7K) | unknown ❌ | aad ✅ (new code: `auth.identity_failed`) | + +### Related Issues +| Issue | Status | Relationship | +|---|---|---| +| **#7233** | 🟢 PR #7235 open | Direct fix | +| #7104 "Revisit UserContextError categories" | 🟡 Open | Downstream — better error buckets for agents | + +--- + +## Opportunity 3: `azd up` Reliability (31% Success Rate) + +### Scale +- **49,154 failures/28d**, **11,110 users** +- The flagship compound command (provision + deploy) +- Failure cascades: a provision failure = `up` failure even if deploy would succeed + +### Error Breakdown for `azd up` +| Error Category | Count | Users | Preventable? | +|---|---|---|---| +| `error.suggestion` | 7,036 | 1,928 | ⚠️ Partially — many are auth errors wrapped in suggestions | +| `arm.deployment.failed` | 4,725 | 1,466 | ❌ Infrastructure errors (quota, policy, region) | +| `arm.400` | 2,540 | 940 | ⚠️ Some are preventable with validation | +| `Docker.missing` | 1,028 | 540 | ✅ **100% preventable** with pre-flight check | + +### Existing Work +| Issue/PR | Status | Coverage | +|---|---|---| +| **#7234** / PR #7236 | 🟢 PR open, CI green | Auth pre-flight + `--check` flag for agents | +| **#7240** Docker pre-flight | 🟡 Open (assigned spboyer) | Suggest remoteBuild when Docker missing | +| **#7179** Preflight: Azure Policy blocking local auth | 🟢 PR open (vhvb1989) | Detects policy-blocked deployments early | +| **#7115** Never abort deployment on validation errors | 🟡 Open | More resilient deploy behavior | +| **#3533** Verify dependency tools per service | 🟡 Open (good first issue) | Check only tools needed, not all | + +### Recommended Next Steps +1. **Ship #7234** (auth pre-flight middleware) — prevents late auth failures +2. **Ship #7240** (Docker pre-flight) — catch Docker/tool missing before starting +3. **Decompose `up` failure reporting** — show provision vs deploy failures separately so users know what succeeded +4. **Add ARM quota pre-flight** — check quota before deploying (prevents the most common ARM failures) + +--- + +## Opportunity 4: `internal.errors_errorString` — Untyped Errors + +### Scale +- **462,344 errors/28d**, **16,978 users** +- These are bare `errors.New()` calls without typed sentinels +- Each one is a classification opportunity lost + +### What's Happening +Code paths that return `errors.New("something failed")` or `fmt.Errorf("failed: %w", err)` where the inner error is also untyped end up in the catch-all bucket with the opaque name `errors_errorString`. + +### Recommended Next Steps +1. **Audit hot paths** — Find the top `errors.New()` call sites in auth token, provision, and deploy code paths +2. **Add typed sentinels progressively** — Start with the highest-volume error messages +3. **Test enforcement** — The test suite already has `allowedCatchAll` enforcement (errors_test.go line 791). Expand this pattern to prevent regressions. + +--- + +## Opportunity 5: AI Agent Optimization + +### Scale +- **420K executions/28d** from AI agents (~4% of total, growing fast) +- Claude Code: **367K executions**, 699 users (exploding growth) +- Copilot CLI: **50K executions**, 579 users (using MCP tools) + +### Key Pain Points +| Issue | Impact | Status | +|---|---|---| +| `auth token` failure rate: 61% (Copilot CLI), 41% (Claude Code) | Agents retry in loops, wasting cycles | 🟢 PR #7236 (`--check` flag) | +| No machine-readable auth status | Agents parse error messages | 🟢 PR #7236 (`expiresOn` in JSON) | +| Agent init errors lack remediation hints | Adoption barrier | 🟡 #6820 open | +| Extension compatibility issues | `env refresh` broken with agents ext | 🟡 #7195 open | + +### Existing Work +| Issue/PR | Status | Coverage | +|---|---|---| +| **#7234** / PR #7236 | 🟢 PR open, CI green | `azd auth token --check` + `expiresOn` in status | +| **#7202** Evaluation and testing framework | 🟢 PR open (spboyer) | Systematic testing for agent flows | +| **#6820** Agent init error remediation hints | 🟡 Open | Better error UX for agent setup | +| **#7195** `env refresh` + agent extension | 🟡 Open | Compatibility fix | +| **#7156** Hosted agent toolsets | 🟡 Open | Azure AI Foundry integration | + +--- + +## Opportunity 6: ARM Deployment Resilience + +### Scale +- **22,033 `arm.deployment.failed`/28d**, 4,025 users +- **6,426 `arm.validate.failed`/28d**, 1,220 users +- **6,371 `arm.400`/28d**, 1,479 users + +### Existing Work +| Issue/PR | Status | Coverage | +|---|---|---| +| **#6793** Investigate 500/503 ARM errors | 🟡 Open | ~707 preventable transient failures | +| **#7179** Preflight: Azure Policy detection | 🟢 PR open | Catches policy blocks before deploy | + +### Recommended Next Steps +1. **Add retry logic for transient 500/503** (#6793) — ~707 preventable failures +2. **Pre-flight quota check** — many ARM 400s are quota exceeded +3. **Surface inner deployment error codes** in user-facing messages + +--- + +## Opportunity 7: Tool Installation UX + +### Scale +- **Docker.missing**: 2,936 failures, 784 users +- **tool.docker.failed**: 4,655 failures, 1,024 users +- **tool.dotnet.failed**: 3,732 failures, 1,345 users +- **tool.bicep.failed**: 5,099 failures, 1,437 users + +### Existing Work +| Issue/PR | Status | Coverage | +|---|---|---| +| **#7240** Docker pre-flight + remoteBuild suggestion | 🟡 Open (assigned spboyer) | Detect upfront, suggest alternative | +| **#5715** Docker missing → suggest remoteBuild | 🟡 Open | Original request (superseded by #7240) | +| **#3533** Per-service tool validation | 🟡 Open (good first issue) | Only check needed tools | +| **#6424** Pre-tool-validation lifecycle event | 🟡 Open | Extension hook for guidance | + +--- + +## Deeper Questions To Investigate + +These are questions the current data could answer but the report doesn't address: + +### Metric Integrity +1. **Report two success rates** — Overall (64.5%) AND excluding auth token (~94%). The current headline is misleading; `auth token` is 80% of all executions and dominates every metric. + +### `azd up` Decomposition +2. **Stage-level failure attribution** — Where in the `up` flow does it fail? 70% at provision, 20% at deploy, 10% at both? Without this, we can't prioritize which stage to fix first. + +### Hidden Problem Commands +3. **`azd restore` at 60% success** — 1,993 failures, 693 users. A restore operation failing 40% of the time is a red flag nobody's investigating. +4. **`env get-value` at 84.3%** — A read-only key lookup failing 15.7% for 10,748 users. Likely missing env/key/project — but should be surfaced as a UX problem, not swallowed. +5. **`auth login` at 88.3%** — 24,476 login failures. Login should approach 100%. What's actually failing? + +### Template & Customer Analysis +6. **Template × command success matrix** — `azure-search-openai-demo` has 259K executions. If that template has a high failure rate, it skews numbers for 2,542 users. Need per-template success rates. +7. **Strategic account success variance** — EY at 71.6% vs Allianz at 99.8%. Same tool, 28-point gap. Understanding WHY (template? region? subscription policies?) enables targeted customer success. + +### Error Quality +8. **What's inside `error.suggestion`?** — 33,762 errors classified as `error.suggestion` but the inner error varies wildly. The `classifySuggestionType` inner code exists in telemetry — should be surfaced alongside the wrapper code. + +### User Journey & Retention +9. **End-to-end funnel** — `init` (91%) → `provision` (58.8%) → `deploy` (80.8%). What's the **cohort completion rate**? If a user inits, what % ever successfully deploy? +10. **First-run vs repeat failure rate** — Are tool failures (Docker, Bicep, dotnet) concentrated on first-run users or recurring? First-run = onboarding UX fix. Recurring = bug. +11. **Failure → churn correlation** — Engaged (2+ days) is only 4,072/18,811 MAU (21.6%). Are users who hit `azd up` failures coming back? If not, the 31% success rate is also a **retention problem**. + +### AI Agent Journey +12. **Agent → deployment conversion** — Agents call `auth token` heavily but how many get to `provision`/`deploy`? What's the agent user journey funnel vs desktop users? + +--- + +## Priority Matrix (Updated) + +| Priority | Opportunity | Volume | Effort | Status | +|---|---|---|---|---| +| 🟢 **In Review** | UnknownError classification | 2.35M | Medium | PR #7241 | +| 🟢 **In Review** | Auth error categories | 610K | Small | PR #7235 | +| 🟢 **In Review** | Auth pre-flight + agent `--check` | 610K cascade | Medium | PR #7236 | +| 🟡 **Filed** | Docker pre-flight | 2.9K + 4.7K | Small | #7240 | +| 🟡 **P1** | `azd up` decomposition | 49K | Medium | Needs Kusto query | +| 🟡 **P2** | `errors_errorString` audit | 462K | Large (ongoing) | No active issue | +| 🟡 **P2** | Template success matrix | Unknown | Small (query) | Needs Kusto query | +| 🟡 **P2** | `restore` / `env get-value` investigation | 4K | Small | No active issue | +| 🟡 **P2** | ARM resilience | 35K | Medium | #6793, #7179 | +| 🟢 **P3** | AI agent journey analysis | 420K | Small (query) | Needs Kusto query | + +--- + +*Cross-referenced against Azure/azure-dev open issues and PRs as of 2026-03-23.* diff --git a/tmp/reports/docker-missing-investigation.md b/tmp/reports/docker-missing-investigation.md new file mode 100644 index 00000000000..d73308a36a1 --- /dev/null +++ b/tmp/reports/docker-missing-investigation.md @@ -0,0 +1,101 @@ +# Docker Missing: Investigation & Implementation Plan + +**Generated:** 2026-03-22 +**Issue:** #5715, #3533 +**Telemetry:** 2,936 failures, 784 users / 28 days + +--- + +## Problem + +When Docker is not installed, `azd deploy`/`up` fails with `tool.Docker.missing` but **doesn't tell users they can use `remoteBuild: true`** — a built-in alternative that builds containers on Azure instead of locally. + +This affects Container Apps, AKS, and some Function App deployments where Docker is listed as required but isn't actually needed when remote builds are enabled. + +--- + +## Architecture + +``` +azd deploy + → projectManager.EnsureServiceTargetTools() + → containerHelper.RequiredExternalTools() + → if remoteBuild: true → [] (no Docker needed!) + → if remoteBuild: false/nil → [docker] (Docker required) + → tools.EnsureInstalled(docker) + → docker.CheckInstalled() → MissingToolErrors +``` + +### Key Insight + +`ContainerHelper.RequiredExternalTools()` (`pkg/project/container_helper.go:250-260`) already skips Docker when `remoteBuild: true`. The user just doesn't know this option exists. + +--- + +## What Supports remoteBuild + +| Service Target | remoteBuild Support | Default | Docker Required Without It | +|---|---|---|---| +| **Container Apps** | ✅ Yes | Not set | Yes | +| **AKS** | ✅ Yes | Not set | Yes | +| **Function App** (Flex) | ✅ Yes | JS/TS/Python: true | Varies | +| **App Service** | ❌ No | N/A | Yes (if containerized) | +| **Static Web App** | ❌ No | N/A | No | + +--- + +## Proposed Fix + +### Where to change: `internal/cmd/deploy.go` (after `EnsureServiceTargetTools`) + +When `MissingToolErrors` is caught with Docker in the tool list: + +1. Check which services actually need Docker (considering remoteBuild support) +2. If all services support remoteBuild → suggest it as primary alternative +3. If some do, some don't → suggest it for eligible services + install Docker for the rest + +### Error flow (proposed): + +```go +if toolErr, ok := errors.AsType[*tools.MissingToolErrors](err); ok { + if slices.Contains(toolErr.ToolNames, "Docker") { + // Check if services can use remoteBuild instead + return nil, &internal.ErrorWithSuggestion{ + Err: toolErr, + Suggestion: "Your services can build on Azure instead of locally. " + + "Add 'docker: { remoteBuild: true }' to each service in azure.yaml, " + + "or install Docker: https://aka.ms/azure-dev/docker-install", + } + } +} +``` + +### Files to modify + +| File | Change | +|---|---| +| `cli/azd/internal/cmd/deploy.go` | Catch Docker missing, wrap with remoteBuild suggestion | +| `cli/azd/pkg/project/project_manager.go` | Add helper to detect remoteBuild-capable services | +| `cli/azd/pkg/tools/docker/docker.go` | Optionally enhance base error message | +| `cli/azd/internal/cmd/deploy_test.go` | Test for suggestion when Docker missing | + +### Same pattern needed in: +- `internal/cmd/provision.go` (calls `EnsureAllTools`) +- `internal/cmd/up.go` (compound command) + +--- + +## Expected Impact + +- **2,936 failures/28d** get actionable guidance instead of a dead-end error +- **784 users** learn about remoteBuild as an alternative +- Users without Docker can still deploy to Container Apps and AKS +- Aligns with existing `ErrorWithSuggestion` pattern used throughout the codebase + +--- + +## Related Issues + +- **#5715** — "When docker is missing, suggest to use remoteBuild: true" (open) +- **#3533** — "Operations should verify presence of dependency tools needed for service" (open, good first issue) +- **#7239** — UnknownError investigation (newly created P0) From eadfb9efcfe6135033d081e49cdbc7bc1048ca6d Mon Sep 17 00:00:00 2001 From: Shayne Boyer Date: Mon, 23 Mar 2026 14:09:21 -0700 Subject: [PATCH 04/11] Remove tmp/ from tracking, add to .gitignore Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .gitignore | 1 + tmp/codebase-scan-results.md | 395 ----------- tmp/go-1.26-modernization-analysis.md | 623 ------------------ tmp/reports/azd-telemetry-28d-report.md | 342 ---------- .../azd-telemetry-opportunities-report.md | 231 ------- tmp/reports/docker-missing-investigation.md | 101 --- 6 files changed, 1 insertion(+), 1692 deletions(-) delete mode 100644 tmp/codebase-scan-results.md delete mode 100644 tmp/go-1.26-modernization-analysis.md delete mode 100644 tmp/reports/azd-telemetry-28d-report.md delete mode 100644 tmp/reports/azd-telemetry-opportunities-report.md delete mode 100644 tmp/reports/docker-missing-investigation.md diff --git a/.gitignore b/.gitignore index 1e834015454..d1c941990d2 100644 --- a/.gitignore +++ b/.gitignore @@ -78,3 +78,4 @@ cli/azd/extensions/microsoft.azd.concurx/concurx.exe cli/azd/extensions/azure.appservice/azureappservice cli/azd/extensions/azure.appservice/azureappservice.exe .squad/ +tmp/ diff --git a/tmp/codebase-scan-results.md b/tmp/codebase-scan-results.md deleted file mode 100644 index 9288cf2a641..00000000000 --- a/tmp/codebase-scan-results.md +++ /dev/null @@ -1,395 +0,0 @@ -# Go Modernization Analysis — Azure Developer CLI - -**Date:** 2025-07-22 -**Go Version:** 1.26 -**Branch:** `feat/local-fallback-remote-build` -**Scope:** `cli/azd/pkg/`, `cli/azd/cmd/`, `cli/azd/internal/` - ---- - -## Executive Summary - -The codebase already makes strong use of modern Go features: `slices`, `maps`, `slices.Sorted(maps.Keys(...))`, -`slices.Contains`, `maps.Clone`, `context.WithoutCancel`, and `errors.Join` are all present. The remaining -modernization opportunities are mostly **low-to-medium impact** incremental cleanups. - -| Category | Occurrences | Impact | Effort | -|---|---|---|---| -| 1. sort.Slice → slices.SortFunc | 16 | Medium | Low | -| 2. interface{} → any | 7 (non-generated) | Low | Low | -| 3. math.Min(float64) → built-in min | 1 | Low | Trivial | -| 4. sync.Map → generic typed map | 13 | Medium | Medium | -| 5. http.NewRequest → WithContext | 6 | Medium | Low | -| 6. sort.Strings → slices.Sort | 2 (non-test) | Low | Trivial | -| 7. Manual keys collect → slices.Sorted(maps.Keys) | 1 | Low | Trivial | -| 8. for i := 0; i < n; i++ → range n | 6 | Low | Trivial | -| 9. Manual slice clone → slices.Clone | 1 real case | Low | Trivial | -| 10. log.Printf → structured logging | 309 | High | High | -| 11. C-style for loops in parsers | 3 | Low | Low | - ---- - -## 1. `sort.Slice` / `sort.Sort` / `sort.Strings` → `slices` package - -**Count:** 16 total (10 in non-test code) - -The project already uses `slices.Sort`, `slices.SortFunc`, and `slices.Sorted(maps.Keys(...))` extensively -(~266 `slices.*` / `maps.*` usages). These 16 remaining `sort.*` calls are stragglers. - -**Non-test files to update:** - -| File | Line | Current | Replacement | -|---|---|---|---| -| `pkg/infra/provisioning_progress_display.go` | 182 | `sort.Slice(newlyDeployedResources, ...)` | `slices.SortFunc(newlyDeployedResources, ...)` | -| `pkg/extensions/manager.go` | 350 | `sort.Sort(semver.Collection(availableVersions))` | Keep — `semver.Collection` implements `sort.Interface`; no `slices` equivalent without wrapper | -| `pkg/account/subscriptions_manager.go` | 329 | `sort.Slice(allSubscriptions, ...)` | `slices.SortFunc(allSubscriptions, ...)` | -| `pkg/account/subscriptions.go` | 84, 139, 168 | `sort.Slice(...)` | `slices.SortFunc(...)` | -| `internal/agent/tools/io/file_search.go` | 146 | `sort.Strings(secureMatches)` | `slices.Sort(secureMatches)` | -| `internal/telemetry/appinsights-exporter/transmitter.go` | 186 | `sort.Sort(result.Response.Errors)` | Keep — custom `sort.Interface` type | - -**Recommendation:** Replace `sort.Slice` → `slices.SortFunc` and `sort.Strings` → `slices.Sort` where the type -is a plain slice (not implementing `sort.Interface`). Use `cmp.Compare` for the comparison function. - -**Example transformation:** -```go -// Before -sort.Slice(allSubscriptions, func(i, j int) bool { - return allSubscriptions[i].Name < allSubscriptions[j].Name -}) - -// After -slices.SortFunc(allSubscriptions, func(a, b Subscription) int { - return cmp.Compare(a.Name, b.Name) -}) -``` - -**Impact:** Medium — improves type safety and readability, removes `sort` import in several files. - ---- - -## 2. `interface{}` → `any` - -**Count:** 140 total, but **133 are in generated `.pb.go` files** (protobuf/gRPC stubs). - -**Non-generated occurrences (7):** - -| File | Line | Code | -|---|---|---| -| `pkg/llm/github_copilot.go` | 205 | `func saveToFile(filePath string, data interface{}) error` | -| `pkg/llm/github_copilot.go` | 220 | `func loadFromFile(filePath string, data interface{}) error` | -| `pkg/llm/github_copilot.go` | 410 | `var copilotResp map[string]interface{}` | -| `pkg/azapi/deployments.go` | 339-340 | Comment: `interface{}` | -| `internal/grpcserver/project_service.go` | 420 | Comment: `interface{}` | -| `internal/grpcserver/project_service.go` | 714 | Comment: `interface{}` | - -**Recommendation:** Run `go fix ./...` which should auto-convert `interface{}` → `any`. The generated `.pb.go` -files are controlled by protobuf codegen and should be left as-is (they'll update when protos are regenerated). - -**Impact:** Low — cosmetic modernization. Only 4 actual code changes needed (3 in `github_copilot.go`, 1 in `deployments.go`). - ---- - -## 3. `math.Min(float64(...), float64(...))` → built-in `min()` - -**Count:** 1 - -| File | Line | Current | -|---|---|---| -| `pkg/account/subscriptions_manager.go` | 304 | `numWorkers := int(math.Min(float64(len(tenants)), float64(maxWorkers)))` | - -**Recommendation:** -```go -// Before -numWorkers := int(math.Min(float64(len(tenants)), float64(maxWorkers))) - -// After -numWorkers := min(len(tenants), maxWorkers) -``` - -**Impact:** Low — single occurrence, but a clean improvement. Removes the awkward float64 casting. - ---- - -## 4. `sync.Map` → Generic Typed Map - -**Count:** 13 usages across 8 files - -| File | Line | Field | -|---|---|---| -| `internal/cmd/add/add_select_ai.go` | 357 | `var sharedResults sync.Map` | -| `pkg/grpcbroker/message_broker.go` | 81-82 | `responseChans sync.Map`, `handlers sync.Map` | -| `pkg/auth/credential_providers.go` | 27 | `tenantCredentials sync.Map` | -| `pkg/devcentersdk/developer_client.go` | 38 | `cache sync.Map` | -| `pkg/ux/canvas.go` | 153 | `items sync.Map` | -| `pkg/ai/model_service.go` | 218, 304 | `var sharedResults sync.Map` | -| `pkg/containerapps/container_app.go` | 115-116 | `appsClientCache sync.Map`, `jobsClientCache sync.Map` | - -**Recommendation:** Consider creating a generic `SyncMap[K, V]` wrapper or using a third-party typed -concurrent map. Go 1.23+ doesn't add a generic `sync.Map` in stdlib, but a project-level utility would -eliminate `any` casts at every `Load`/`Store` call site. - -```go -// Utility type -type SyncMap[K comparable, V any] struct { - m sync.Map -} - -func (s *SyncMap[K, V]) Load(key K) (V, bool) { - v, ok := s.m.Load(key) - if !ok { - var zero V - return zero, false - } - return v.(V), true -} -``` - -**Impact:** Medium — removes type assertions at every call site, prevents type mismatch bugs. - ---- - -## 5. `http.NewRequest` → `http.NewRequestWithContext` - -**Count:** 6 non-test occurrences using `http.NewRequest` without context - -| File | Line | -|---|---| -| `tools/avmres/main.go` | 193 | -| `internal/telemetry/appinsights-exporter/transmitter.go` | 80 | -| `extensions/azure.ai.agents/internal/project/parser.go` | 1041, 1106, 1161 | -| `pkg/llm/github_copilot.go` | 388 | - -The codebase already uses `http.NewRequestWithContext` in 30 other places — these 6 are inconsistent. - -**Recommendation:** Replace all `http.NewRequest(...)` with `http.NewRequestWithContext(ctx, ...)` to ensure -proper cancellation propagation. If no context is available, use `context.Background()` explicitly. - -**Impact:** Medium — ensures cancellation and timeout propagation works correctly for HTTP calls. - ---- - -## 6. `sort.Strings` → `slices.Sort` - -**Count:** 2 non-test files - -| File | Line | Current | -|---|---|---| -| `internal/agent/tools/io/file_search.go` | 146 | `sort.Strings(secureMatches)` | -| `extensions/azure.ai.finetune/internal/cmd/validation.go` | 75 | `sort.Strings(missingFlags)` | - -**Recommendation:** `slices.Sort(secureMatches)` — direct 1:1 replacement, no behavior change. - -**Impact:** Low — trivial cleanup. - ---- - -## 7. Manual Map Keys Collection → `slices.Sorted(maps.Keys(...))` - -**Count:** 1 remaining instance (the codebase already uses the modern pattern in 6+ places) - -| File | Line | Current Pattern | -|---|---|---| -| `pkg/azdext/scope_detector.go` | 116-120 | Manual `keys := make([]string, 0, len(m)); for k := range m { keys = append(keys, k) }; slices.Sort(keys)` | - -**Recommendation:** -```go -// Before (5 lines) -keys := make([]string, 0, len(opts.CustomRules)) -for k := range opts.CustomRules { - keys = append(keys, k) -} -slices.Sort(keys) - -// After (1 line) -keys := slices.Sorted(maps.Keys(opts.CustomRules)) -``` - -**Impact:** Low — single occurrence, but a good example of the pattern the rest of the codebase already follows. - ---- - -## 8. C-style `for i := 0; i < n; i++` → `for i := range n` - -**Count:** 6 candidates - -| File | Line | Current | -|---|---|---| -| `pkg/password/generator_test.go` | 84 | `for i := 0; i < len(choices); i++` | -| `pkg/yamlnode/yamlnode.go` | 235, 297 | `for i := 0; i < len(s); i++` — character-by-character parsing | -| `pkg/apphost/eval.go` | 33 | `for i := 0; i < len(src); i++` — character parsing | -| `pkg/output/table.go` | 141 | `for i := 0; i < v.Len(); i++` — reflect.Value iteration | -| `cmd/extensions.go` | 56 | `for i := 0; i < len(namespaceParts)-1; i++` | - -**Recommendation:** Only convert simple iteration patterns. The character-parsing loops in `yamlnode.go` and -`eval.go` modify `i` inside the loop body (e.g., `i++` to skip chars), so they **cannot** use `range n`. - -Convertible: -- `pkg/password/generator_test.go:84` → `for i := range len(choices)` -- `cmd/extensions.go:56` → `for i := range len(namespaceParts)-1` - -Not convertible (loop variable modified inside body): -- `pkg/yamlnode/yamlnode.go:235,297` — skip -- `pkg/apphost/eval.go:33` — skip -- `pkg/output/table.go:141` — uses `reflect.Value.Len()`, fine to convert: `for i := range v.Len()` - -**Impact:** Low — readability improvement for simple cases. - ---- - -## 9. Manual Slice Clone → `slices.Clone` - -**Count:** 82 total matches for `append([]T{}, ...)`, but most are in generated `.pb.go` files or -are actually prepend patterns (building a new slice with a prefix element), not clones. - -**Actual clone candidate (1):** - -| File | Line | Current | -|---|---|---| -| `pkg/project/service_target_external.go` | 298 | `return append([]string{}, endpointsResp.Endpoints...), nil` | - -**Recommendation:** -```go -// Before -return append([]string{}, endpointsResp.Endpoints...), nil - -// After -return slices.Clone(endpointsResp.Endpoints), nil -``` - -Most other `append([]T{}, ...)` patterns are prepend operations (e.g., `append([]string{cmd}, args...)`), -which are correct as-is — `slices.Clone` wouldn't apply. - -**Impact:** Low — single real clone candidate. - ---- - -## 10. `log.Printf` / `log.Println` → Structured Logging - -**Count:** 309 non-test occurrences of `log.Print*` - -**Hotspot files:** - -| File | Count (approx) | -|---|---| -| `pkg/pipeline/pipeline_manager.go` | ~20+ | -| `pkg/tools/pack/pack.go` | ~10 | -| `pkg/tools/github/github.go` | ~10 | -| `pkg/cmdsubst/secretOrRandomPassword.go` | 2 | - -**Recommendation:** This is the **highest-volume** modernization opportunity. Consider adopting `log/slog` -for structured, leveled logging. However, this is a large-effort change that would require: - -1. Defining a project-wide `slog.Handler` configuration -2. Replacing all `log.Printf` calls with `slog.Info`, `slog.Debug`, etc. -3. Ensuring log levels map correctly (many current `log.Printf` calls are debug-level) - -The codebase already has an internal tracing/telemetry system (`internal/tracing/`), so `slog` adoption -should integrate with that. - -**Impact:** High value, but **high effort**. Best done as a dedicated initiative, not incremental cleanup. - ---- - -## 11. Context Patterns - -**Count:** ~90 context usage sites - -The codebase already uses modern context patterns well: -- `context.WithoutCancel` — used in `main.go` and tests (Go 1.21+) ✅ -- `context.WithCancel`, `context.WithTimeout` — standard usage ✅ -- `context.AfterFunc` — not used (Go 1.21+, niche utility) - -**No actionable items** — context usage is modern. - ---- - -## 12. Error Handling Patterns - -**Count:** 2,742 `errors.New` / `fmt.Errorf` occurrences; 12 `errors.Join` usages - -The project already uses `errors.Join` where multi-error aggregation is needed. Error wrapping with -`fmt.Errorf("...: %w", err)` is the standard pattern and is used correctly throughout. - -**No actionable items** — error handling follows Go best practices. - ---- - -## 13. File I/O Patterns - -**Count:** 0 `ioutil.*` usages ✅ - -The codebase has already migrated from `io/ioutil` (deprecated in Go 1.16) to `os.ReadFile`, -`os.WriteFile`, etc. No action needed. - ---- - -## 14. Iterator Patterns (`iter.Seq` / `iter.Seq2`) - -**Count:** 0 direct usages - -The `iter` package (Go 1.23+) is not used directly, though `slices.Collect(maps.Keys(...))` and -range-over-func are used implicitly via the `slices`/`maps` packages. - -**Recommendation:** No immediate action. Iterator patterns are most useful for custom collection types; -the codebase doesn't have strong candidates for custom iterators currently. - ---- - -## 15. JSON Handling - -**Count:** 309 `json.Marshal`/`Unmarshal`/`NewDecoder`/`NewEncoder` usages - -Standard `encoding/json` usage throughout. Go 1.24+ introduced `encoding/json/v2` as an experiment, -but it's not stable yet. - -**No actionable items.** - ---- - -## 16. Crypto/TLS - -**Count:** 13 usages - -TLS configuration in `pkg/httputil/util.go` correctly sets `MinVersion: tls.VersionTLS12`. Standard -crypto usage (`crypto/sha256`, `crypto/rand`) throughout. - -**No actionable items.** - ---- - -## Priority Recommendations - -### Quick Wins (< 1 hour each) - -1. **`math.Min` → built-in `min`** — 1 file, 1 line -2. **`sort.Strings` → `slices.Sort`** — 2 files -3. **Manual keys collect → `slices.Sorted(maps.Keys(...))`** — 1 file -4. **`interface{}` → `any`** via `go fix ./...` — automatic -5. **`slices.Clone` for actual clone** — 1 file - -### Medium Effort (1-4 hours) - -6. **`sort.Slice` → `slices.SortFunc`** — ~8 non-test files -7. **`http.NewRequest` → `http.NewRequestWithContext`** — 6 files -8. **Range-over-int conversions** — 2-3 convertible sites - -### Large Effort (multi-day initiative) - -9. **`sync.Map` → generic typed wrapper** — 8 files, requires design -10. **`log.Printf` → `slog` structured logging** — 309 call sites, requires architecture decision - ---- - -## Already Modern ✅ - -The codebase is **already well-modernized** in several areas: - -- **266 usages** of `slices.*` / `maps.*` packages -- `slices.Sorted(maps.Keys(...))` pattern used in 6+ places -- `maps.Clone`, `maps.Copy` used in pipeline and infra packages -- `slices.Contains`, `slices.ContainsFunc`, `slices.IndexFunc` used extensively -- `slices.DeleteFunc` used for filtering -- `context.WithoutCancel` used correctly -- `errors.Join` used for multi-error scenarios -- Zero `ioutil.*` usages (fully migrated) -- `any` used throughout (only 7 hand-written `interface{}` remain) diff --git a/tmp/go-1.26-modernization-analysis.md b/tmp/go-1.26-modernization-analysis.md deleted file mode 100644 index ce430dcdd3c..00000000000 --- a/tmp/go-1.26-modernization-analysis.md +++ /dev/null @@ -1,623 +0,0 @@ -# Go 1.26 Modernization Analysis — Azure Developer CLI - -**Date:** 2026-03-12 -**Go Version:** 1.26 (go1.26.1 darwin/arm64) -**Analyzed by:** GPT-5.4 (feature research) + Claude Opus 4.6 (codebase scan) -**Scope:** `cli/azd/` — pkg, cmd, internal - ---- - -## Executive Summary - -The azd codebase is **already well-modernized** — 266 usages of `slices`/`maps` packages, zero `ioutil`, and `errors.Join` in use. However, Go 1.24–1.26 introduced several impactful features the project hasn't adopted yet. This document maps new Go features to concrete azd opportunities, organized by impact and effort. - -### Top 5 Opportunities - -| # | Feature | Impact | Effort | Details | -|---|---------|--------|--------|---------| -| 1 | `errors.AsType[T]` (1.26) | 🟢 High | Low | Generic, type-safe error matching — eliminates boilerplate | -| 2 | `WaitGroup.Go` (1.25) | 🟢 High | Medium | Simplifies concurrent goroutine patterns across CLI | -| 3 | `omitzero` JSON tag (1.24) | 🟡 Medium | Low | Better zero-value omission for `time.Time` and optional fields | -| 4 | `os.Root` (1.24/1.25) | 🟡 Medium | Medium | Secure directory-scoped file access | -| 5 | `testing.T.Context` / `B.Loop` (1.24) | 🟡 Medium | Low | Modern test patterns | - ---- - -## Part 1: New Go 1.24–1.26 Features & azd Applicability - -### 🔤 Language Changes - -#### Generic Type Aliases (Go 1.24) -```go -// Now supported: -type Set[T comparable] = map[T]bool -type Result[T any] = struct { Value T; Err error } -``` -**azd applicability:** Could simplify type layering in `pkg/project/` and `pkg/infra/` where wrapper types are common. Low priority — most types are already well-defined. - -#### `new(expr)` — Initialize Pointer from Expression (Go 1.26) -```go -// Before -v := "hello" -ptr := &v - -// After -ptr := new("hello") -``` -**azd applicability:** Useful for optional pointer fields in protobuf/JSON structs. The codebase has several `to.Ptr()` helper patterns that this could partially replace. Medium value in new code. - -#### Self-Referential Generic Constraints (Go 1.26) -```go -type Adder[A Adder[A]] interface { Add(A) A } -``` -**azd applicability:** Niche — useful for F-bounded polymorphism patterns. No immediate need in azd. - ---- - -### ⚡ Performance Improvements (Free Wins) - -These improvements apply automatically with Go 1.26 — no code changes needed: - -| Feature | Source | Expected Impact | -|---------|--------|-----------------| -| **Green Tea GC** (default in 1.26) | Runtime | 10–40% GC overhead reduction | -| **Swiss Tables map impl** (1.24) | Runtime | 2–3% CPU reduction overall | -| **`io.ReadAll` optimization** (1.26) | stdlib | ~2x faster, lower memory | -| **cgo overhead -30%** (1.26) | Runtime | Faster cgo calls | -| **Stack-allocated slice backing** (1.25/1.26) | Compiler | Fewer heap allocations | -| **Container-aware GOMAXPROCS** (1.25) | Runtime | Better perf in containers | -| **`fmt.Errorf` for plain strings** (1.26) | stdlib | Less allocation, closer to `errors.New` | - -**Action:** Simply building with Go 1.26 gets these for free. Consider benchmarking before/after to quantify improvements for azd's specific workload. - ---- - -### 🛠️ Standard Library Additions to Adopt - -#### 1. `errors.AsType[T]` (Go 1.26) — **HIGH IMPACT** - -Generic, type-safe replacement for `errors.As`: - -```go -// Before (current azd pattern — verbose, requires pre-declaring variable) -var httpErr *azcore.ResponseError -if errors.As(err, &httpErr) { - // use httpErr -} - -// After (Go 1.26 — single expression, type-safe) -if httpErr, ok := errors.AsType[*azcore.ResponseError](err); ok { - // use httpErr -} -``` - -**azd occurrences:** The codebase has ~200+ `errors.As` calls. This is the single highest-impact modernization: -- Eliminates pre-declared error variables -- Type is inferred from the generic parameter -- Faster than `errors.As` in many cases - -**Files with heavy `errors.As` usage:** -- `pkg/azapi/` — Azure API error handling -- `pkg/infra/provisioning/` — deployment error classification -- `cmd/` — command error handling -- `internal/cmd/` — action error handling - -#### 2. `sync.WaitGroup.Go` (Go 1.25) — **HIGH IMPACT** - -```go -// Before (current azd pattern) -var wg sync.WaitGroup -for _, item := range items { - wg.Add(1) - go func() { - defer wg.Done() - process(item) - }() -} -wg.Wait() - -// After (Go 1.25) -var wg sync.WaitGroup -for _, item := range items { - wg.Go(func() { - process(item) - }) -} -wg.Wait() -``` - -**azd occurrences:** Multiple goroutine fan-out patterns across: -- `pkg/account/subscriptions_manager.go` — parallel tenant subscription fetching -- `pkg/project/` — parallel service operations -- `pkg/infra/` — parallel resource operations -- `internal/cmd/` — parallel deployment steps - -**Benefits:** Eliminates `Add(1)` + `defer Done()` boilerplate, reduces goroutine leak risk. - -#### 3. `omitzero` JSON Struct Tag (Go 1.24) — **MEDIUM IMPACT** - -```go -// Before — omitempty doesn't work well with time.Time (zero time is not "empty") -type Config struct { - CreatedAt time.Time `json:"createdAt,omitempty"` // BUG: zero time still serialized -} - -// After — omitzero uses IsZero() method -type Config struct { - CreatedAt time.Time `json:"createdAt,omitzero"` // Correct: omits zero time -} -``` - -**azd applicability:** Any struct with `time.Time` fields or custom types implementing `IsZero()`. Check `pkg/config/`, `pkg/project/`, `pkg/account/` for candidates. - -#### 4. `os.Root` / `os.OpenRoot` (Go 1.24, expanded 1.25) — **MEDIUM IMPACT** - -Secure, directory-scoped filesystem access that prevents path traversal: - -```go -root, err := os.OpenRoot("/app/workspace") -if err != nil { ... } -defer root.Close() - -// All operations are confined to /app/workspace -data, err := root.ReadFile("config.yaml") // OK -data, err := root.ReadFile("../../etc/passwd") // Error: escapes root -``` - -**Go 1.25 additions:** `Chmod`, `Chown`, `MkdirAll`, `ReadFile`, `RemoveAll`, `Rename`, `Symlink`, `WriteFile` - -**azd applicability:** -- `pkg/project/` — reading `azure.yaml` and project files -- `pkg/osutil/` — file utility operations -- Extension framework — sandboxing extension file access -- Any user-supplied path handling for security hardening - -#### 5. `testing.T.Context` / `testing.B.Loop` (Go 1.24) — **MEDIUM IMPACT** - -```go -// T.Context — automatic context tied to test lifecycle -func TestFoo(t *testing.T) { - ctx := t.Context() // cancelled when test ends - result, err := myService.Do(ctx) -} - -// B.Loop — cleaner benchmarks -func BenchmarkFoo(b *testing.B) { - for b.Loop() { // replaces for i := 0; i < b.N; i++ - doWork() - } -} -``` - -**azd applicability:** The test suite has 2000+ test functions. `T.Context()` would simplify context creation in tests that currently use `context.Background()` or `context.TODO()`. - -#### 6. `T.Chdir` (Go 1.24) - -```go -// Before -oldDir, _ := os.Getwd() -os.Chdir(tempDir) -defer os.Chdir(oldDir) - -// After -t.Chdir(tempDir) // auto-restored when test ends -``` - -**azd applicability:** Multiple test files change directory manually. `T.Chdir` is safer and cleaner. - -#### 7. `bytes.Buffer.Peek` (Go 1.26) - -Access buffered bytes without consuming them. Useful in streaming/parsing scenarios. - -#### 8. `testing/synctest` (Go 1.25 GA) - -Fake-time testing for concurrent code: - -```go -synctest.Run(func() { - go func() { - time.Sleep(time.Hour) // instant in fake time - ch <- result - }() - synctest.Wait() // wait for all goroutines in bubble -}) -``` - -**azd applicability:** Could simplify testing of timeout/retry logic in `pkg/retry/`, `pkg/httputil/`, and polling operations. - -#### 9. String/Bytes Iterator Helpers (Go 1.24) - -```go -// New iterator-returning functions -for line := range strings.Lines(text) { ... } -for part := range strings.SplitSeq(text, ",") { ... } -for field := range strings.FieldsSeq(text) { ... } -``` - -**azd applicability:** Can replace `strings.Split` + loop patterns where only iteration is needed (avoids allocating the intermediate slice). - -#### 10. `testing.T.Attr` / `T.ArtifactDir` (Go 1.25/1.26) - -Structured test metadata and artifact directories for richer test output. - ---- - -### 🔧 Tooling Changes to Leverage - -#### `go fix` Modernizers (Go 1.26) — **Use Now** - -The rewritten `go fix` tool includes automatic modernizers: -```bash -go fix ./... -``` -This will automatically: -- Convert `interface{}` → `any` -- Simplify loop patterns -- Apply other Go version-appropriate modernizations - -**Recommendation:** Run `go fix ./...` as a first pass before any manual changes. - -#### `tool` Directives in `go.mod` (Go 1.24) - -``` -// go.mod -tool ( - golang.org/x/tools/cmd/stringer - github.com/golangci/golangci-lint/cmd/golangci-lint -) -``` - -Replaces `tools.go` pattern for declaring tool dependencies. Pinned versions, cached execution. - -**azd applicability:** The project likely uses build tools that could be declared here. - -#### `go.mod` `ignore` Directive (Go 1.25) - -Ignore directories that shouldn't be part of the module: -``` -ignore vendor/legacy -``` - -**azd applicability:** Could be useful for the extensions directory structure. - -#### `//go:fix inline` (Go 1.26) - -Mark functions for source-level inlining by `go fix`: -```go -//go:fix inline -func deprecated() { newFunction() } -``` - -**azd applicability:** Useful when deprecating internal helper functions — `go fix` will inline callers automatically. - ---- - -## Part 2: Codebase Scan — Specific Modernization Opportunities - -### Quick Wins (< 1 hour total) - -#### A. `math.Min` → built-in `min()` (1 site) - -| File | Line | -|------|------| -| `pkg/account/subscriptions_manager.go` | 304 | - -```go -// Before -numWorkers := int(math.Min(float64(len(tenants)), float64(maxWorkers))) -// After -numWorkers := min(len(tenants), maxWorkers) -``` - -#### B. `sort.Strings` → `slices.Sort` (2 files) - -| File | Line | -|------|------| -| `internal/agent/tools/io/file_search.go` | 146 | -| `extensions/azure.ai.finetune/internal/cmd/validation.go` | 75 | - -#### C. Manual keys collect → `slices.Sorted(maps.Keys(...))` (1 file) - -| File | Line | -|------|------| -| `pkg/azdext/scope_detector.go` | 116-120 | - -```go -// Before (5 lines) -keys := make([]string, 0, len(opts.CustomRules)) -for k := range opts.CustomRules { keys = append(keys, k) } -slices.Sort(keys) -// After (1 line) -keys := slices.Sorted(maps.Keys(opts.CustomRules)) -``` - -#### D. Manual slice clone → `slices.Clone` (1 file) - -| File | Line | -|------|------| -| `pkg/project/service_target_external.go` | 298 | - -```go -// Before -return append([]string{}, endpointsResp.Endpoints...), nil -// After -return slices.Clone(endpointsResp.Endpoints), nil -``` - -#### E. Run `go fix ./...` (automatic) - -Converts remaining `interface{}` → `any` (7 hand-written occurrences) and applies other modernizations. - ---- - -### Medium Effort (1–4 hours each) - -#### F. `sort.Slice` → `slices.SortFunc` (~8 non-test files) - -| File | Pattern | -|------|---------| -| `pkg/infra/provisioning_progress_display.go:182` | `sort.Slice(newlyDeployedResources, ...)` | -| `pkg/account/subscriptions_manager.go:329` | `sort.Slice(allSubscriptions, ...)` | -| `pkg/account/subscriptions.go:84,139,168` | `sort.Slice(...)` | -| + 3 more files | — | - -```go -// Before -sort.Slice(items, func(i, j int) bool { return items[i].Name < items[j].Name }) -// After -slices.SortFunc(items, func(a, b Item) int { return cmp.Compare(a.Name, b.Name) }) -``` - -#### G. `http.NewRequest` → `http.NewRequestWithContext` (6 files) - -| File | -|------| -| `tools/avmres/main.go:193` | -| `internal/telemetry/appinsights-exporter/transmitter.go:80` | -| `extensions/azure.ai.agents/internal/project/parser.go:1041,1106,1161` | -| `pkg/llm/github_copilot.go:388` | - -#### H. Range-over-int conversions (2-3 sites) - -| File | Convertible? | -|------|-------------| -| `pkg/password/generator_test.go:84` | ✅ Yes | -| `cmd/extensions.go:56` | ✅ Yes | -| `pkg/output/table.go:141` | ✅ Yes (reflect) | -| `pkg/yamlnode/yamlnode.go:235,297` | ❌ No (modifies i) | -| `pkg/apphost/eval.go:33` | ❌ No (modifies i) | - ---- - -### Large Initiatives (Multi-day) - -#### I. `sync.Map` → Generic Typed Wrapper (13 usages, 8 files) - -Create a `SyncMap[K, V]` utility type to eliminate type assertions: - -```go -type SyncMap[K comparable, V any] struct { m sync.Map } - -func (s *SyncMap[K, V]) Load(key K) (V, bool) { ... } -func (s *SyncMap[K, V]) Store(key K, value V) { ... } -func (s *SyncMap[K, V]) Delete(key K) { ... } -func (s *SyncMap[K, V]) Range(f func(K, V) bool) { ... } -``` - -**Files to update:** -- `internal/cmd/add/add_select_ai.go` -- `pkg/grpcbroker/message_broker.go` -- `pkg/auth/credential_providers.go` -- `pkg/devcentersdk/developer_client.go` -- `pkg/ux/canvas.go` -- `pkg/ai/model_service.go` -- `pkg/containerapps/container_app.go` - -#### J. `log.Printf` → `slog` Structured Logging (309 sites) - -This is the **highest-volume opportunity** but requires an architecture decision: -- Define project-wide `slog.Handler` configuration -- Integrate with existing `internal/tracing/` system -- Map current `log.Printf` calls to appropriate `slog.Info`/`slog.Debug` levels -- Consider `--debug` flag integration - -**Recommendation:** Treat as a separate initiative with its own design doc. - -#### K. `errors.AsType` Migration (200+ sites) - -Systematic migration of `errors.As` → `errors.AsType[T]` across the codebase. - -**Recommendation:** Can be done incrementally — start with the highest-traffic error handling paths in `pkg/azapi/` and `pkg/infra/`. - ---- - -## Part 3: Testing Modernization - -### Current State -- **2000+ test functions** across the codebase -- Uses `testify/mock` for mocking -- Table-driven tests are the standard pattern - -### Opportunities - -| Feature | Where to Apply | Impact | -|---------|---------------|--------| -| `T.Context()` | Tests using `context.Background()` | Cleaner test lifecycle | -| `T.Chdir()` | Tests with manual `os.Chdir` | Safer directory changes | -| `B.Loop()` | Benchmarks using `for i := 0; i < b.N; i++` | Cleaner benchmarks | -| `testing/synctest` | Timeout/retry/polling tests | Deterministic timing | -| `T.ArtifactDir()` | Tests generating output files | Organized test artifacts | -| `testing/cryptotest.SetGlobalRandom` | Crypto tests needing determinism | Reproducible crypto tests | - ---- - -## Part 4: Security Improvements - -| Feature | Description | azd Relevance | -|---------|-------------|---------------| -| `os.Root` (1.24/1.25) | Path-traversal-safe file access | Project file reading, extension sandbox | -| `net/http.CrossOriginProtection` (1.25) | Built-in CSRF via Fetch Metadata | Local dev server callbacks | -| Post-quantum TLS (1.26) | `SecP256r1MLKEM768`, `SecP384r1MLKEM1024` | Automatic via Go runtime | -| RSA min 1024-bit (1.24) | Enforced by crypto/rsa | Automatic | -| Heap base randomization (1.26) | ASLR improvement | Automatic | - ---- - -## Part 5: PR-Sized Adoption Roadmap - -Each item below is scoped to a single, small PR that can be reviewed independently. - ---- - -### PR 1: `go fix` automated modernizations -**Files:** All `.go` files (auto-applied) -**Changes:** `interface{}` → `any`, loop simplifications, other `go fix` modernizers -**Review notes:** Pure mechanical transform — reviewer just confirms no behavioral changes -**Commands:** -```bash -go fix ./... -gofmt -s -w . -``` - ---- - -### PR 2: Built-in `min()` and `slices.Sort` quick wins -**Files:** 3 files, ~5 lines changed -**Changes:** -- `pkg/account/subscriptions_manager.go`: `math.Min(float64, float64)` → `min()` -- `internal/agent/tools/io/file_search.go`: `sort.Strings` → `slices.Sort` -- `extensions/azure.ai.finetune/internal/cmd/validation.go`: `sort.Strings` → `slices.Sort` - ---- - -### PR 3: `slices.Clone` and `slices.Sorted(maps.Keys(...))` one-liners -**Files:** 2 files, ~6 lines changed -**Changes:** -- `pkg/project/service_target_external.go`: `append([]T{}, s...)` → `slices.Clone(s)` -- `pkg/azdext/scope_detector.go`: manual keys collect + sort → `slices.Sorted(maps.Keys(...))` - ---- - -### PR 4: `sort.Slice` → `slices.SortFunc` in account package -**Files:** 2 files (~4 call sites) -**Changes:** -- `pkg/account/subscriptions_manager.go`: `sort.Slice` → `slices.SortFunc` with `cmp.Compare` -- `pkg/account/subscriptions.go`: 3 × `sort.Slice` → `slices.SortFunc` - ---- - -### PR 5: `sort.Slice` → `slices.SortFunc` in remaining packages -**Files:** ~4 files -**Changes:** -- `pkg/infra/provisioning_progress_display.go` -- Other non-test files with `sort.Slice` -- Does **not** touch `sort.Sort` on `sort.Interface` types (leave those as-is) - ---- - -### PR 6: `http.NewRequest` → `http.NewRequestWithContext` -**Files:** 6 files -**Changes:** -- `tools/avmres/main.go` -- `internal/telemetry/appinsights-exporter/transmitter.go` -- `extensions/azure.ai.agents/internal/project/parser.go` (3 sites) -- `pkg/llm/github_copilot.go` -- Thread existing `ctx` or use `context.Background()` explicitly - ---- - -### PR 7: Range-over-int modernization -**Files:** 3 files, ~3 lines each -**Changes:** -- `pkg/password/generator_test.go`: `for i := 0; i < len(choices); i++` → `for i := range len(choices)` -- `cmd/extensions.go`: similar -- `pkg/output/table.go`: `for i := 0; i < v.Len(); i++` → `for i := range v.Len()` -- Skips parser loops where `i` is modified inside the body - ---- - -### PR 8: Generic `SyncMap[K, V]` utility type -**Files:** 1 new file + 8 files updated -**Changes:** -- Create `pkg/sync/syncmap.go` with `SyncMap[K, V]` generic wrapper -- Update 8 files to use typed map instead of `sync.Map` + type assertions - ---- - -### PR 9: Adopt `errors.AsType[T]` — Azure API error paths -**Files:** `pkg/azapi/` (focused scope) -**Changes:** -- Replace `var errT *T; errors.As(err, &errT)` → `errT, ok := errors.AsType[*T](err)` -- Start with the highest-traffic error handling paths - ---- - -### PR 10: Adopt `errors.AsType[T]` — infra/provisioning -**Files:** `pkg/infra/provisioning/` and `pkg/infra/` -**Changes:** Same pattern as PR 9, scoped to infrastructure error handling - ---- - -### PR 11: Adopt `errors.AsType[T]` — commands and actions -**Files:** `cmd/` and `internal/cmd/` -**Changes:** Same pattern, scoped to command-level error handling - ---- - -### PR 12: Adopt `WaitGroup.Go` in concurrent patterns -**Files:** 4-6 files with goroutine fan-out -**Changes:** -- `pkg/account/subscriptions_manager.go`: parallel tenant fetching -- `pkg/project/`: parallel service operations -- Other files with `wg.Add(1); go func() { defer wg.Done(); ... }()` pattern -- Replace with `wg.Go(func() { ... })` - ---- - -### PR 13: Test modernization — `T.Context()` adoption (batch 1) -**Files:** ~20 test files (scoped to one package, e.g., `pkg/project/`) -**Changes:** -- Replace `context.Background()` / `context.TODO()` → `t.Context()` in tests -- One package at a time to keep PRs reviewable - ---- - -### PR 14: Test modernization — `T.Chdir()` adoption -**Files:** Test files with manual `os.Chdir` + defer restore -**Changes:** Replace with `t.Chdir(dir)` — auto-restored when test ends - ---- - -### Future PRs (require design discussion first) - -| PR | Topic | Notes | -|----|-------|-------| -| 15+ | `omitzero` JSON tags | Audit all JSON structs with `time.Time` fields | -| 16+ | `os.Root` secure file access | Needs design doc for project file reading | -| 17+ | `slog` structured logging | 309 sites — needs architecture decision | -| 18+ | `testing/synctest` for retry/timeout tests | Evaluate which tests benefit | -| 19+ | `tool` directives in `go.mod` | Replace `tools.go` pattern | - ---- - -## Appendix A: Already Modern ✅ - -The codebase already excels in these areas: -- **266 usages** of `slices.*` / `maps.*` packages -- `slices.Sorted(maps.Keys(...))` pattern in 6+ places -- `maps.Clone`, `maps.Copy` used correctly -- `context.WithoutCancel` used where appropriate -- `errors.Join` for multi-error aggregation -- Zero `ioutil.*` — fully migrated -- `any` used throughout (only 7 hand-written `interface{}` remain) - -## Appendix B: No Action Needed - -| Area | Status | -|------|--------| -| `ioutil` migration | ✅ Complete | -| `context.WithoutCancel` | ✅ Already used | -| `errors.Join` | ✅ Already used (12 sites) | -| `slices.Contains` | ✅ Used extensively | -| Crypto/TLS config | ✅ Modern (TLS 1.2 minimum) | -| File I/O patterns | ✅ Uses `os.ReadFile`/`os.WriteFile` | diff --git a/tmp/reports/azd-telemetry-28d-report.md b/tmp/reports/azd-telemetry-28d-report.md deleted file mode 100644 index 1eceb672e7c..00000000000 --- a/tmp/reports/azd-telemetry-28d-report.md +++ /dev/null @@ -1,342 +0,0 @@ -# Azure Developer CLI (azd) — Rolling 28-Day Telemetry Report - -**Period:** Feb 21 – Mar 18, 2026 -**Source:** `ddazureclients.kusto.windows.net` / `DevCli` database -**Generated:** 2026-03-21 - ---- - -## Executive Summary - -| Metric | Value | -|--------|-------| -| **Total Executions** | 10,178,008 | -| **Unique Users** (DevDeviceId) | 129,003 | -| **Overall Success Rate** | 64.5% | -| **Unique Templates Used** | 2,533 | -| **Unique Azure Subscriptions** | 9,183 | -| **Unique Customer Orgs** | 3,857+ | - -### Monthly KPI Comparison (from AzdKPIs table) - -| KPI | Jan 2026 | Feb 2026 | Δ | -|-----|----------|----------|---| -| MAU (Active1d) | 16,636 | 18,811 | **+13.1%** | -| Engaged (2d) | 3,950 | 4,072 | +3.1% | -| Dedicated (10d) | 369 | 364 | -1.4% | -| Provisions | 59,152 | 63,881 | +8.0% | -| Deployments | 54,958 | 56,728 | +3.2% | -| Successful Provisions | 28,835 | 30,742 | +6.6% | -| Successful Deployments | 41,058 | 43,186 | +5.2% | -| Azure Subscriptions | 6,420 | 7,339 | **+14.3%** | - ---- - -## Daily Active Users Trend - -| Date | DAU | Executions | -|------|-----|-----------| -| Feb 21 (Fri) | 504 | 26,306 | -| Feb 22 (Sat) | 3,131 | 142,943 | -| Feb 23 (Sun) | 9,505 | 434,029 | -| Feb 24 (Mon) | 11,499 | 575,189 | -| Feb 25 (Tue) | 11,570 | 622,173 | -| Feb 26 (Wed) | 10,742 | 481,576 | -| Feb 27 (Thu) | 9,665 | 397,341 | -| Feb 28 (Fri) | 3,566 | 223,880 | -| Mar 1 (Sat) | 3,244 | 254,988 | -| Mar 2 (Sun) | 10,093 | 491,753 | -| Mar 3 (Mon) | 10,522 | 458,583 | -| Mar 4 (Tue) | 10,458 | 580,816 | -| Mar 5 (Wed) | 10,359 | 518,339 | -| Mar 6 (Thu) | 9,489 | 500,349 | -| Mar 7 (Fri) | 3,506 | 247,362 | -| Mar 8 (Sat) | 3,267 | 168,655 | -| Mar 9 (Sun) | 10,356 | 563,312 | -| Mar 10 (Mon) | 11,275 | 519,414 | -| Mar 11 (Tue) | 11,004 | 382,957 | -| Mar 12 (Wed) | 10,802 | 425,650 | -| Mar 13 (Thu) | 10,106 | 311,655 | -| Mar 14 (Fri) | 3,586 | 348,198 | -| Mar 15 (Sat) | 3,397 | 162,191 | -| Mar 16 (Sun) | 10,616 | 333,201 | -| Mar 17 (Mon) | 11,241 | 558,356 | -| Mar 18 (Tue) | 11,699 | 448,755 | - -> Weekday DAU averages ~10,500–11,500 users. Weekend dips to ~3,200–3,600. Pattern is healthy and consistent. - ---- - -## Top Commands (by executions) - -| Command | Executions | Unique Users | Success % | -|---------|-----------|-------------|-----------| -| `azd auth token` | 8,237,762 | 20,692 | 58.9% | -| `azd env set` | 428,612 | 33,758 | 99.5% | -| `azd auth login` | 208,951 | 72,188 | 88.3% | -| `azd env get-values` | 190,212 | 24,792 | 98.9% | -| `azd env get-value` | 173,396 | 10,748 | 84.3% | -| `azd env list` | 153,117 | 18,883 | 99.1% | -| **`azd provision`** | **127,640** | **40,469** | **58.8%** | -| **`azd deploy`** | **115,974** | **43,735** | **80.8%** | -| `azd package` | 76,273 | 20,476 | 92.8% | -| **`azd up`** | **71,276** | **11,110** | **31.0%** ⚠️ | -| `azd config set` | 62,626 | 40,166 | 100% | -| `azd env new` | 47,364 | 29,470 | 93.5% | -| `azd init` | 46,133 | 6,251 | 91.0% | -| `azd env select` | 39,631 | 26,229 | 84.2% | - ---- - -## Top Templates (by unique users) - -| Template | Users | Executions | -|----------|-------|-----------| -| **azd-init** (interactive init) | 8,427 | 81,078 | -| **azure-search-openai-demo** | 2,542 | 259,785 | -| **chat-with-your-data-solution-accelerator** | 625 | 5,707 | -| **multi-agent-custom-automation-engine** | 550 | 19,742 | -| **todo-nodejs-mongo** | 469 | 1,925 | -| **agentic-applications-for-unified-data-foundation** | 461 | 12,653 | - ---- - -## Execution Environments — IDEs & AI Agents - -### All Execution Environments - -| Environment | Executions | Unique Users | User Share | -|-------------|-----------|-------------|------------| -| **Desktop** (terminal) | 8,699,096 | 39,274 | 30.4% | -| **GitHub Actions** | 421,968 | 37,759 | 29.2% | -| **Azure Pipelines** | 358,116 | 32,543 | 25.2% | -| **VS Code** (extension) | 93,024 | 16,006 | 12.4% | -| **Visual Studio** | 76,789 | 4,886 | 3.8% | -| **Azure CloudShell** | 19,456 | 3,686 | 2.9% | -| **Claude Code** 🔥 | 367,193 | 699 | 0.5% | -| **GitHub Codespaces** | 36,097 | 1,066 | 0.8% | -| **GitLab CI** | 36,519 | 1,142 | 0.9% | -| **GitHub Copilot CLI** | 50,115 | 579 | 0.4% | -| **OpenCode** | 3,204 | 38 | — | -| **Gemini** | 102 | 12 | — | - -### 🤖 AI/LLM Agent Deep Dive - -#### Weekly Trend - -| Week | Claude Code Users | Claude Code Exec | Copilot CLI Users | Copilot CLI Exec | OpenCode Users | Gemini Users | -|------|------------------|-----------------|-------------------|-----------------|---------------|-------------| -| Feb 16 | 20 | 1,213 | 9 | 177 | 3 | — | -| Feb 23 | 202 | 28,918 | 185 | 12,362 | 17 | 6 | -| Mar 2 | 263 | 133,593 | 228 | 18,716 | 11 | 2 | -| Mar 9 | **263** | **159,470** | 218 | 11,015 | 14 | 2 | -| Mar 16 | 218 | 43,999 | 173 | 7,845 | 12 | 2 | - -#### Success Rates Over Time - -| Week | Claude Code | Copilot CLI | OpenCode | Gemini | -|------|------------|------------|----------|--------| -| Feb 16 | 23.1% | 79.1% | 75.0% | — | -| Feb 23 | 31.2% | 74.4% | 71.2% | 62.0% | -| Mar 2 | 46.4% | 48.5% | 24.5% | 69.2% | -| Mar 9 | **87.9%** | 69.5% | 66.0% | 80.0% | -| Mar 16 | 82.5% | 63.1% | 74.8% | 20.0% | - -#### Top Commands by AI Agent - -**Claude Code** (367K executions, 699 users): - -| Command | Executions | Users | -|---------|-----------|-------| -| `auth token` | 284,257 | 375 | -| `env list` | 63,530 | 171 | -| `env get-values` | 5,804 | 217 | -| `env set` | 3,859 | 186 | -| `deploy` | 2,920 | 192 | -| `provision` | 1,198 | 170 | -| `up` | 709 | 125 | - -**GitHub Copilot CLI** (50K executions, 579 users): - -| Command | Executions | Users | -|---------|-----------|-------| -| `auth token` | 18,303 | 319 | -| `env get-value` | 6,747 | 69 | -| `env set` | 4,140 | 203 | -| `env get-values` | 3,766 | 214 | -| `deploy` | 3,043 | 187 | -| `provision` | 2,957 | 208 | -| `up` | 1,903 | 172 | - -> Notable: Copilot CLI is also running **MCP operations** (`mcp.validate_azure_yaml`, `mcp.iac_generation_rules`, `mcp.infrastructure_generation`, etc.) — 70 total MCP calls from 19+ users. - -#### AI Agent Key Insights - -- 🔥 **Claude Code is exploding** — went from 20 users to 263 in 4 weeks, with 367K executions (more than Copilot CLI despite fewer users). Heavy `auth token` loop suggests agentic workflow patterns. -- **Claude Code success rate improved dramatically** — from 23% to 88% in 4 weeks, suggesting rapid integration maturation. -- **Copilot CLI** is steady at ~200 users/week with 50K executions. Already using azd's MCP tools. -- **AI agents collectively**: ~1,300 users, 420K executions — about **4% of total azd volume** and growing fast. - ---- - -## Customer Breakdown - -### By Segment - -| Segment | Customers | Users | Subscriptions | Executions | -|---------|-----------|-------|---------------|------------| -| **SMB Commercial** | 1,852 | 26,637 | 2,732 | 473K | -| **Unspecified** | 993 | 21,622 | 2,466 | 445K | -| **Upper Majors Commercial** | 336 | 11,325 | 588 | 83K | -| **Strategic Commercial** | 188 | 5,054 | 564 | 113K | -| **Corporate Commercial** | 234 | 3,370 | 822 | 41K | -| **Upper Majors Public Sector** | 118 | 2,434 | 175 | 19K | -| Majors Growth Commercial | 34 | 418 | 44 | 5K | -| Strategic Public Sector | 29 | 264 | 49 | 5K | - -### Top Customers (by unique users) - -| Customer | Segment | Country | Users | Subs | Executions | Success% | -|----------|---------|---------|-------|------|------------|----------| -| **CoreAI - Platform & Tools** | Internal | 🇺🇸 US | 9,246 | 35 | 73K | 86.9% | -| **Investec Bank** | Upper Majors | 🇬🇧 UK | 6,471 | 74 | 15K | 90.3% | -| **Microsoft** | Internal | 🇺🇸 US | 3,976 | 978 | 149K | 82.8% | -| **Cloud + AI** | Internal | 🇨🇳 CN | 3,066 | 183 | 107K | 96.3% | -| **Vee Friends** | SMB | 🇺🇸 US | 2,585 | 1 | 8K | 99.0% | -| **Grupo Cosan** | Upper Majors | 🇧🇷 BR | 1,667 | 1 | 5K | 100% | -| **Puerto Rico PRITS** | Public Sector | 🇵🇷 PR | 1,422 | 2 | 4K | 94.9% | -| **H&M** | Strategic | 🇸🇪 SE | 1,107 | 40 | 8K | 91.7% | -| **Jersey Telecoms** | Corporate | 🇬🇧 UK | 951 | 2 | 7K | 99.6% | -| **Volkswagen** | Strategic | 🇩🇪 DE | 664 | 17 | 39K | 98.6% | -| **Microsoft Security** | Internal | 🇺🇸 US | 640 | 15 | 14K | 95.7% | -| **IBM** | Strategic | 🇺🇸 US | 385 | 7 | 4K | 83.2% | -| **Deloitte** | Strategic | 🇺🇸 US | 329 | 10 | 1K | 96.3% | -| **bp** | Strategic | 🇬🇧 UK | 271 | 9 | 6K | 97.9% | -| **Allianz Technology** | Strategic | 🇩🇪 DE | 233 | 8 | 11K | 99.8% | -| **Rabobank** | Strategic | 🇳🇱 NL | 248 | 8 | 462 | 92.2% | -| **EY Global** | Strategic | 🇺🇸 US | 201 | 25 | 2K | 71.6% | -| **Mercedes-Benz** | Strategic | 🇩🇪 DE | 195 | 6 | 3K | 99.0% | - ---- - -## Error Analysis - -### Overall Failure Rate Trend - -| Week | Total Exec | Failures | Fail Rate | -|------|-----------|----------|-----------| -| Feb 16 | 168K | 87K | 52.0% | -| Feb 23 | 2,989K | 1,050K | 35.1% | -| Mar 2 | 2,966K | 1,015K | 34.2% | -| Mar 9 | 2,713K | 948K | 35.0% | -| Mar 16 | 1,340K | 513K | 38.3% | - -### Success Rates by Command - -| Command | Total | Failures | Success% | Users | -|---------|-------|----------|----------|-------| -| `auth token` | 8,236,682 | 3,384,478 | **58.9%** | 20,689 | -| `auth login` | 208,930 | 24,476 | 88.3% | 72,184 | -| `provision` | 127,630 | 52,639 | **58.8%** | 40,466 | -| `deploy` | 115,961 | 22,243 | 80.8% | 43,735 | -| `package` | 76,268 | 5,520 | 92.8% | 20,476 | -| **`up`** | **71,271** | **49,154** | **31.0%** ⚠️ | 11,110 | -| `env new` | 47,355 | 3,098 | 93.5% | 29,463 | -| `init` | 46,132 | 4,171 | 91.0% | 6,251 | -| `down` | 15,830 | 2,761 | 82.6% | 2,982 | -| `restore` | 4,979 | 1,993 | **60.0%** | 693 | - -### Top Result Codes (failure reasons) - -| Result Code | Count | Users | Category | -|-------------|-------|-------|----------| -| `UnknownError` | 2,383,049 | 8,128 | Uncategorized | -| `auth.login_required` | 547,007 | 1,984 | Auth | -| `internal.errors_errorString` | 462,344 | 16,978 | Internal | -| `auth.not_logged_in` | 66,331 | 1,769 | Auth | -| `error.suggestion` | 33,762 | 7,475 | User guidance | -| `service.arm.deployment.failed` | 22,033 | 4,025 | ARM | -| `service.aad.failed` | 13,619 | 1,276 | AAD | -| `internal.azidentity_AuthenticationFailedError` | 8,732 | 196 | Auth (identity) | -| `service.arm.validate.failed` | 6,426 | 1,220 | ARM | -| `service.arm.400` | 6,371 | 1,479 | ARM | -| `tool.bicep.failed` | 5,099 | 1,437 | Bicep | -| `tool.pwsh.failed` | 4,931 | 688 | PowerShell | -| `tool.docker.failed` | 4,655 | 1,024 | Docker | -| `tool.dotnet.failed` | 3,732 | 1,345 | .NET | -| `user.canceled` | 3,630 | 1,739 | User action | -| `tool.Docker.missing` | 2,936 | 784 | Docker not installed | -| `tool.terraform.failed` | 2,872 | 355 | Terraform | - -### Provision / Deploy / Up Error Breakdown - -| Command | Error Category | Result Code | Count | Users | -|---------|---------------|-------------|-------|-------| -| `provision` | ARM | arm.deployment.failed | 12,395 | 3,908 | -| `up` | ARM | error.suggestion | 7,036 | 1,928 | -| `up` | ARM | arm.deployment.failed | 4,725 | 1,466 | -| `provision` | ARM | arm.validate.failed | 3,393 | 1,208 | -| `provision` | ARM | arm.400 | 3,130 | 1,143 | -| `provision` | Bicep | bicep.failed | 2,715 | 1,311 | -| `up` | ARM | arm.400 | 2,540 | 940 | -| `provision` | PowerShell | pwsh.failed | 2,053 | 560 | -| `provision` | Terraform | terraform.failed | 1,873 | 335 | -| `deploy` | Docker | docker.failed | 1,652 | 692 | -| `up` | Docker | Docker.missing | 1,028 | 540 | -| `deploy` | .NET | dotnet.failed | 1,304 | 559 | - -### 🔍 `cmd.auth.token` Error Deep Dive - -`auth token` accounts for **3.38M of 3.61M total failures (94%)**. Breakdown: - -| Result Code | Error Category | Count | Users | % of Auth Token Failures | -|-------------|---------------|-------|-------|--------------------------| -| `UnknownError` | unknown | 2,352,993 | 6,403 | **69.5%** | -| `auth.login_required` | unknown | 543,972 | 1,353 | **16.1%** | -| `internal.errors_errorString` | unknown | 398,164 | 5,285 | **11.8%** | -| `auth.not_logged_in` | unknown | 65,643 | 1,233 | 1.9% | -| `service.aad.failed` | aad | 12,246 | 658 | 0.4% | -| `internal.azidentity_AuthenticationFailedError` | unknown | 7,258 | 65 | 0.2% | - -> ⚠️ **Classification gap:** 99.6% of auth token failures are categorized as "unknown" even when result codes like `auth.login_required` and `auth.not_logged_in` provide clear signals. Only `service.aad.failed` (12K) gets properly categorized. - -#### Auth Token Failure Rate by Execution Environment - -| Environment | Total | Failures | Fail % | -|-------------|-------|----------|--------| -| **Desktop** | 7,888,323 | 3,250,684 | **41.2%** | -| **Claude Code** | 284,257 | 116,750 | **41.1%** | -| **GitHub Copilot CLI** | 18,302 | 11,156 | **61.0%** ⚠️ | -| **GitHub Actions** | 12,915 | 383 | 3.0% ✅ | -| **GitHub Codespaces** | 10,580 | 1,417 | 13.4% | -| **Azure Pipelines** | 9,692 | 2,219 | 22.9% | -| **GitLab CI** | 5,147 | 0 | 0% ✅ | -| **OpenCode** | 2,053 | 908 | 44.2% | - -> CI/CD environments have near-zero auth token failures (service principal auth). Interactive/agent environments hit ~41-61% failure rates due to token expiry and "not logged in" retries. - ---- - -## Key Takeaways & Recommendations - -### 📈 Growth Signals -1. **MAU up 13% MoM**, Azure subscriptions up **14%** — strong growth trajectory -2. **AI agent adoption is hockey-sticking** — Claude Code went from 20→263 users in 4 weeks -3. **3,857+ customer organizations** actively using azd, with marquee enterprise logos (VW, H&M, bp, Mercedes, Allianz, IBM) - -### ⚠️ Areas of Concern -1. **`azd up` at 31% success rate** — the flagship command. Compounds provision + deploy failures. 49K failures affecting 11K users. -2. **`auth token` dominates failure volume** — 3.38M failures, but most are expected retry patterns (token refresh). Inflates overall failure rate from ~6% (excluding auth token) to ~35%. -3. **Error classification is broken** — 2.35M errors classified as `UnknownError` with no detail. `auth.login_required` (544K) is categorized as "unknown" despite having a clear result code. - -### 🔧 Suggested Actions -1. **Fix error categorization** — Map `auth.login_required`, `auth.not_logged_in`, and `azidentity_AuthenticationFailedError` result codes to an `auth` error category instead of `unknown`. -2. **Investigate `UnknownError`** (2.35M) — Add better error introspection to surface what's actually failing. -3. **Improve `azd up` reliability** — At 31% success, the compound command needs better pre-flight checks, clearer error messages, and possibly staged rollback. -4. **Address Docker-not-installed** (2,936 failures, 784 users) — Better pre-req detection and user guidance before attempting container deployments. -5. **Optimize for AI agents** — Claude Code and Copilot CLI are growing fast. Consider agent-specific auth flows (non-interactive token acquisition) and reducing the `auth token` retry loop noise. -6. **Monitor EY Global** — 71.6% success rate for a Strategic account is below the 90%+ benchmark seen at other enterprise customers. - ---- - -*Report generated from `AzdOperations`, `AzdKPIs`, and `Templates` tables on the `ddazureclients` Kusto cluster.* diff --git a/tmp/reports/azd-telemetry-opportunities-report.md b/tmp/reports/azd-telemetry-opportunities-report.md deleted file mode 100644 index 6ca6f121e49..00000000000 --- a/tmp/reports/azd-telemetry-opportunities-report.md +++ /dev/null @@ -1,231 +0,0 @@ -# azd Telemetry: Improvement Opportunities Report - -**Period:** Rolling 28 days (Feb 21 – Mar 18, 2026) -**Generated:** 2026-03-22 (updated 2026-03-23) -**Source:** Telemetry report + GitHub issue/PR cross-reference - ---- - -## Executive Summary - -azd processes **10.2M executions/28d** with a **64.5% overall success rate**. However, this headline number is misleading — `auth token` alone accounts for **3.38M of 3.61M failures (94%)**. Excluding auth token, the real command failure rate is closer to **~6%**. - -The three largest improvement opportunities are: - -| Opportunity | Volume Impact | Status | -|---|---|---| -| 1. **UnknownError bucket** | 2.35M errors with zero classification | 🟢 PR #7241 (in review) | -| 2. **Auth error misclassification** | 610K errors in wrong category | 🟢 PR #7235 (all CI green) | -| 3. **`azd up` at 31% success** | 49K failures, 11K users | 🟢 PR #7236 (auth pre-flight, all CI green) | - ---- - -## Opportunity 1: UnknownError — The Biggest Blind Spot - -### Scale -- **2,352,993 errors/28d** classified as `UnknownError` -- **6,403 users** affected -- **69.5%** of all `auth token` failures -- Currently provides **zero diagnostic signal** - -### Root Cause (confirmed) -`EndWithStatus(err)` in several high-volume code paths bypasses `MapError()` — the central error classification function. The telemetry converter then falls back to `"UnknownFailure"` when the span status description is empty or unrecognized. - -### Fix: PR #7241 (in review) -- MCP tool handler and Copilot agent spans now route through `MapError()` -- `EndWithStatus` fallback prefixed with `internal.` for consistency - -### Existing Work -| Issue/PR | Status | Coverage | -|---|---|---| -| **#7239** / PR #7241 | 🟢 PR open, CI green | Routes spans through MapError | -| #6796 "Reduce Unknown error bucket" | ✅ Closed (partial fix) | Addressed some categories | -| #6576 "Follow up on unknown errors classification" | ✅ Closed | Follow-up from #6796 | -| #5743 "Better unknown error breakdowns" | ✅ Closed | Added `errorType()` introspection | - ---- - -## Opportunity 2: Auth Error Classification ✅ IN PROGRESS - -### Scale -- **610K errors/28d** miscategorized as `"unknown"` instead of `"aad"` -- **~17%** of all auth token errors - -### Fix: PR #7235 (all CI green, awaiting review) -| Error | Before | After | -|---|---|---| -| `auth.login_required` (544K) | unknown ❌ | aad ✅ | -| `auth.not_logged_in` (66K) | unknown ❌ | aad ✅ | -| `azidentity.AuthenticationFailedError` (8.7K) | unknown ❌ | aad ✅ (new code: `auth.identity_failed`) | - -### Related Issues -| Issue | Status | Relationship | -|---|---|---| -| **#7233** | 🟢 PR #7235 open | Direct fix | -| #7104 "Revisit UserContextError categories" | 🟡 Open | Downstream — better error buckets for agents | - ---- - -## Opportunity 3: `azd up` Reliability (31% Success Rate) - -### Scale -- **49,154 failures/28d**, **11,110 users** -- The flagship compound command (provision + deploy) -- Failure cascades: a provision failure = `up` failure even if deploy would succeed - -### Error Breakdown for `azd up` -| Error Category | Count | Users | Preventable? | -|---|---|---|---| -| `error.suggestion` | 7,036 | 1,928 | ⚠️ Partially — many are auth errors wrapped in suggestions | -| `arm.deployment.failed` | 4,725 | 1,466 | ❌ Infrastructure errors (quota, policy, region) | -| `arm.400` | 2,540 | 940 | ⚠️ Some are preventable with validation | -| `Docker.missing` | 1,028 | 540 | ✅ **100% preventable** with pre-flight check | - -### Existing Work -| Issue/PR | Status | Coverage | -|---|---|---| -| **#7234** / PR #7236 | 🟢 PR open, CI green | Auth pre-flight + `--check` flag for agents | -| **#7240** Docker pre-flight | 🟡 Open (assigned spboyer) | Suggest remoteBuild when Docker missing | -| **#7179** Preflight: Azure Policy blocking local auth | 🟢 PR open (vhvb1989) | Detects policy-blocked deployments early | -| **#7115** Never abort deployment on validation errors | 🟡 Open | More resilient deploy behavior | -| **#3533** Verify dependency tools per service | 🟡 Open (good first issue) | Check only tools needed, not all | - -### Recommended Next Steps -1. **Ship #7234** (auth pre-flight middleware) — prevents late auth failures -2. **Ship #7240** (Docker pre-flight) — catch Docker/tool missing before starting -3. **Decompose `up` failure reporting** — show provision vs deploy failures separately so users know what succeeded -4. **Add ARM quota pre-flight** — check quota before deploying (prevents the most common ARM failures) - ---- - -## Opportunity 4: `internal.errors_errorString` — Untyped Errors - -### Scale -- **462,344 errors/28d**, **16,978 users** -- These are bare `errors.New()` calls without typed sentinels -- Each one is a classification opportunity lost - -### What's Happening -Code paths that return `errors.New("something failed")` or `fmt.Errorf("failed: %w", err)` where the inner error is also untyped end up in the catch-all bucket with the opaque name `errors_errorString`. - -### Recommended Next Steps -1. **Audit hot paths** — Find the top `errors.New()` call sites in auth token, provision, and deploy code paths -2. **Add typed sentinels progressively** — Start with the highest-volume error messages -3. **Test enforcement** — The test suite already has `allowedCatchAll` enforcement (errors_test.go line 791). Expand this pattern to prevent regressions. - ---- - -## Opportunity 5: AI Agent Optimization - -### Scale -- **420K executions/28d** from AI agents (~4% of total, growing fast) -- Claude Code: **367K executions**, 699 users (exploding growth) -- Copilot CLI: **50K executions**, 579 users (using MCP tools) - -### Key Pain Points -| Issue | Impact | Status | -|---|---|---| -| `auth token` failure rate: 61% (Copilot CLI), 41% (Claude Code) | Agents retry in loops, wasting cycles | 🟢 PR #7236 (`--check` flag) | -| No machine-readable auth status | Agents parse error messages | 🟢 PR #7236 (`expiresOn` in JSON) | -| Agent init errors lack remediation hints | Adoption barrier | 🟡 #6820 open | -| Extension compatibility issues | `env refresh` broken with agents ext | 🟡 #7195 open | - -### Existing Work -| Issue/PR | Status | Coverage | -|---|---|---| -| **#7234** / PR #7236 | 🟢 PR open, CI green | `azd auth token --check` + `expiresOn` in status | -| **#7202** Evaluation and testing framework | 🟢 PR open (spboyer) | Systematic testing for agent flows | -| **#6820** Agent init error remediation hints | 🟡 Open | Better error UX for agent setup | -| **#7195** `env refresh` + agent extension | 🟡 Open | Compatibility fix | -| **#7156** Hosted agent toolsets | 🟡 Open | Azure AI Foundry integration | - ---- - -## Opportunity 6: ARM Deployment Resilience - -### Scale -- **22,033 `arm.deployment.failed`/28d**, 4,025 users -- **6,426 `arm.validate.failed`/28d**, 1,220 users -- **6,371 `arm.400`/28d**, 1,479 users - -### Existing Work -| Issue/PR | Status | Coverage | -|---|---|---| -| **#6793** Investigate 500/503 ARM errors | 🟡 Open | ~707 preventable transient failures | -| **#7179** Preflight: Azure Policy detection | 🟢 PR open | Catches policy blocks before deploy | - -### Recommended Next Steps -1. **Add retry logic for transient 500/503** (#6793) — ~707 preventable failures -2. **Pre-flight quota check** — many ARM 400s are quota exceeded -3. **Surface inner deployment error codes** in user-facing messages - ---- - -## Opportunity 7: Tool Installation UX - -### Scale -- **Docker.missing**: 2,936 failures, 784 users -- **tool.docker.failed**: 4,655 failures, 1,024 users -- **tool.dotnet.failed**: 3,732 failures, 1,345 users -- **tool.bicep.failed**: 5,099 failures, 1,437 users - -### Existing Work -| Issue/PR | Status | Coverage | -|---|---|---| -| **#7240** Docker pre-flight + remoteBuild suggestion | 🟡 Open (assigned spboyer) | Detect upfront, suggest alternative | -| **#5715** Docker missing → suggest remoteBuild | 🟡 Open | Original request (superseded by #7240) | -| **#3533** Per-service tool validation | 🟡 Open (good first issue) | Only check needed tools | -| **#6424** Pre-tool-validation lifecycle event | 🟡 Open | Extension hook for guidance | - ---- - -## Deeper Questions To Investigate - -These are questions the current data could answer but the report doesn't address: - -### Metric Integrity -1. **Report two success rates** — Overall (64.5%) AND excluding auth token (~94%). The current headline is misleading; `auth token` is 80% of all executions and dominates every metric. - -### `azd up` Decomposition -2. **Stage-level failure attribution** — Where in the `up` flow does it fail? 70% at provision, 20% at deploy, 10% at both? Without this, we can't prioritize which stage to fix first. - -### Hidden Problem Commands -3. **`azd restore` at 60% success** — 1,993 failures, 693 users. A restore operation failing 40% of the time is a red flag nobody's investigating. -4. **`env get-value` at 84.3%** — A read-only key lookup failing 15.7% for 10,748 users. Likely missing env/key/project — but should be surfaced as a UX problem, not swallowed. -5. **`auth login` at 88.3%** — 24,476 login failures. Login should approach 100%. What's actually failing? - -### Template & Customer Analysis -6. **Template × command success matrix** — `azure-search-openai-demo` has 259K executions. If that template has a high failure rate, it skews numbers for 2,542 users. Need per-template success rates. -7. **Strategic account success variance** — EY at 71.6% vs Allianz at 99.8%. Same tool, 28-point gap. Understanding WHY (template? region? subscription policies?) enables targeted customer success. - -### Error Quality -8. **What's inside `error.suggestion`?** — 33,762 errors classified as `error.suggestion` but the inner error varies wildly. The `classifySuggestionType` inner code exists in telemetry — should be surfaced alongside the wrapper code. - -### User Journey & Retention -9. **End-to-end funnel** — `init` (91%) → `provision` (58.8%) → `deploy` (80.8%). What's the **cohort completion rate**? If a user inits, what % ever successfully deploy? -10. **First-run vs repeat failure rate** — Are tool failures (Docker, Bicep, dotnet) concentrated on first-run users or recurring? First-run = onboarding UX fix. Recurring = bug. -11. **Failure → churn correlation** — Engaged (2+ days) is only 4,072/18,811 MAU (21.6%). Are users who hit `azd up` failures coming back? If not, the 31% success rate is also a **retention problem**. - -### AI Agent Journey -12. **Agent → deployment conversion** — Agents call `auth token` heavily but how many get to `provision`/`deploy`? What's the agent user journey funnel vs desktop users? - ---- - -## Priority Matrix (Updated) - -| Priority | Opportunity | Volume | Effort | Status | -|---|---|---|---|---| -| 🟢 **In Review** | UnknownError classification | 2.35M | Medium | PR #7241 | -| 🟢 **In Review** | Auth error categories | 610K | Small | PR #7235 | -| 🟢 **In Review** | Auth pre-flight + agent `--check` | 610K cascade | Medium | PR #7236 | -| 🟡 **Filed** | Docker pre-flight | 2.9K + 4.7K | Small | #7240 | -| 🟡 **P1** | `azd up` decomposition | 49K | Medium | Needs Kusto query | -| 🟡 **P2** | `errors_errorString` audit | 462K | Large (ongoing) | No active issue | -| 🟡 **P2** | Template success matrix | Unknown | Small (query) | Needs Kusto query | -| 🟡 **P2** | `restore` / `env get-value` investigation | 4K | Small | No active issue | -| 🟡 **P2** | ARM resilience | 35K | Medium | #6793, #7179 | -| 🟢 **P3** | AI agent journey analysis | 420K | Small (query) | Needs Kusto query | - ---- - -*Cross-referenced against Azure/azure-dev open issues and PRs as of 2026-03-23.* diff --git a/tmp/reports/docker-missing-investigation.md b/tmp/reports/docker-missing-investigation.md deleted file mode 100644 index d73308a36a1..00000000000 --- a/tmp/reports/docker-missing-investigation.md +++ /dev/null @@ -1,101 +0,0 @@ -# Docker Missing: Investigation & Implementation Plan - -**Generated:** 2026-03-22 -**Issue:** #5715, #3533 -**Telemetry:** 2,936 failures, 784 users / 28 days - ---- - -## Problem - -When Docker is not installed, `azd deploy`/`up` fails with `tool.Docker.missing` but **doesn't tell users they can use `remoteBuild: true`** — a built-in alternative that builds containers on Azure instead of locally. - -This affects Container Apps, AKS, and some Function App deployments where Docker is listed as required but isn't actually needed when remote builds are enabled. - ---- - -## Architecture - -``` -azd deploy - → projectManager.EnsureServiceTargetTools() - → containerHelper.RequiredExternalTools() - → if remoteBuild: true → [] (no Docker needed!) - → if remoteBuild: false/nil → [docker] (Docker required) - → tools.EnsureInstalled(docker) - → docker.CheckInstalled() → MissingToolErrors -``` - -### Key Insight - -`ContainerHelper.RequiredExternalTools()` (`pkg/project/container_helper.go:250-260`) already skips Docker when `remoteBuild: true`. The user just doesn't know this option exists. - ---- - -## What Supports remoteBuild - -| Service Target | remoteBuild Support | Default | Docker Required Without It | -|---|---|---|---| -| **Container Apps** | ✅ Yes | Not set | Yes | -| **AKS** | ✅ Yes | Not set | Yes | -| **Function App** (Flex) | ✅ Yes | JS/TS/Python: true | Varies | -| **App Service** | ❌ No | N/A | Yes (if containerized) | -| **Static Web App** | ❌ No | N/A | No | - ---- - -## Proposed Fix - -### Where to change: `internal/cmd/deploy.go` (after `EnsureServiceTargetTools`) - -When `MissingToolErrors` is caught with Docker in the tool list: - -1. Check which services actually need Docker (considering remoteBuild support) -2. If all services support remoteBuild → suggest it as primary alternative -3. If some do, some don't → suggest it for eligible services + install Docker for the rest - -### Error flow (proposed): - -```go -if toolErr, ok := errors.AsType[*tools.MissingToolErrors](err); ok { - if slices.Contains(toolErr.ToolNames, "Docker") { - // Check if services can use remoteBuild instead - return nil, &internal.ErrorWithSuggestion{ - Err: toolErr, - Suggestion: "Your services can build on Azure instead of locally. " + - "Add 'docker: { remoteBuild: true }' to each service in azure.yaml, " + - "or install Docker: https://aka.ms/azure-dev/docker-install", - } - } -} -``` - -### Files to modify - -| File | Change | -|---|---| -| `cli/azd/internal/cmd/deploy.go` | Catch Docker missing, wrap with remoteBuild suggestion | -| `cli/azd/pkg/project/project_manager.go` | Add helper to detect remoteBuild-capable services | -| `cli/azd/pkg/tools/docker/docker.go` | Optionally enhance base error message | -| `cli/azd/internal/cmd/deploy_test.go` | Test for suggestion when Docker missing | - -### Same pattern needed in: -- `internal/cmd/provision.go` (calls `EnsureAllTools`) -- `internal/cmd/up.go` (compound command) - ---- - -## Expected Impact - -- **2,936 failures/28d** get actionable guidance instead of a dead-end error -- **784 users** learn about remoteBuild as an alternative -- Users without Docker can still deploy to Container Apps and AKS -- Aligns with existing `ErrorWithSuggestion` pattern used throughout the codebase - ---- - -## Related Issues - -- **#5715** — "When docker is missing, suggest to use remoteBuild: true" (open) -- **#3533** — "Operations should verify presence of dependency tools needed for service" (open, good first issue) -- **#7239** — UnknownError investigation (newly created P0) From d321765ab319072e09e6f4920e3916c7c6473799 Mon Sep 17 00:00:00 2001 From: Shayne Boyer Date: Mon, 23 Mar 2026 14:31:18 -0700 Subject: [PATCH 05/11] Revert "Remove tmp/ from tracking, add to .gitignore" This reverts commit 7253f21e3aebf9f26b40363d9fc7deb8d56e34cf. --- .gitignore | 1 - tmp/codebase-scan-results.md | 395 +++++++++++ tmp/go-1.26-modernization-analysis.md | 623 ++++++++++++++++++ tmp/reports/azd-telemetry-28d-report.md | 342 ++++++++++ .../azd-telemetry-opportunities-report.md | 231 +++++++ tmp/reports/docker-missing-investigation.md | 101 +++ 6 files changed, 1692 insertions(+), 1 deletion(-) create mode 100644 tmp/codebase-scan-results.md create mode 100644 tmp/go-1.26-modernization-analysis.md create mode 100644 tmp/reports/azd-telemetry-28d-report.md create mode 100644 tmp/reports/azd-telemetry-opportunities-report.md create mode 100644 tmp/reports/docker-missing-investigation.md diff --git a/.gitignore b/.gitignore index d1c941990d2..1e834015454 100644 --- a/.gitignore +++ b/.gitignore @@ -78,4 +78,3 @@ cli/azd/extensions/microsoft.azd.concurx/concurx.exe cli/azd/extensions/azure.appservice/azureappservice cli/azd/extensions/azure.appservice/azureappservice.exe .squad/ -tmp/ diff --git a/tmp/codebase-scan-results.md b/tmp/codebase-scan-results.md new file mode 100644 index 00000000000..9288cf2a641 --- /dev/null +++ b/tmp/codebase-scan-results.md @@ -0,0 +1,395 @@ +# Go Modernization Analysis — Azure Developer CLI + +**Date:** 2025-07-22 +**Go Version:** 1.26 +**Branch:** `feat/local-fallback-remote-build` +**Scope:** `cli/azd/pkg/`, `cli/azd/cmd/`, `cli/azd/internal/` + +--- + +## Executive Summary + +The codebase already makes strong use of modern Go features: `slices`, `maps`, `slices.Sorted(maps.Keys(...))`, +`slices.Contains`, `maps.Clone`, `context.WithoutCancel`, and `errors.Join` are all present. The remaining +modernization opportunities are mostly **low-to-medium impact** incremental cleanups. + +| Category | Occurrences | Impact | Effort | +|---|---|---|---| +| 1. sort.Slice → slices.SortFunc | 16 | Medium | Low | +| 2. interface{} → any | 7 (non-generated) | Low | Low | +| 3. math.Min(float64) → built-in min | 1 | Low | Trivial | +| 4. sync.Map → generic typed map | 13 | Medium | Medium | +| 5. http.NewRequest → WithContext | 6 | Medium | Low | +| 6. sort.Strings → slices.Sort | 2 (non-test) | Low | Trivial | +| 7. Manual keys collect → slices.Sorted(maps.Keys) | 1 | Low | Trivial | +| 8. for i := 0; i < n; i++ → range n | 6 | Low | Trivial | +| 9. Manual slice clone → slices.Clone | 1 real case | Low | Trivial | +| 10. log.Printf → structured logging | 309 | High | High | +| 11. C-style for loops in parsers | 3 | Low | Low | + +--- + +## 1. `sort.Slice` / `sort.Sort` / `sort.Strings` → `slices` package + +**Count:** 16 total (10 in non-test code) + +The project already uses `slices.Sort`, `slices.SortFunc`, and `slices.Sorted(maps.Keys(...))` extensively +(~266 `slices.*` / `maps.*` usages). These 16 remaining `sort.*` calls are stragglers. + +**Non-test files to update:** + +| File | Line | Current | Replacement | +|---|---|---|---| +| `pkg/infra/provisioning_progress_display.go` | 182 | `sort.Slice(newlyDeployedResources, ...)` | `slices.SortFunc(newlyDeployedResources, ...)` | +| `pkg/extensions/manager.go` | 350 | `sort.Sort(semver.Collection(availableVersions))` | Keep — `semver.Collection` implements `sort.Interface`; no `slices` equivalent without wrapper | +| `pkg/account/subscriptions_manager.go` | 329 | `sort.Slice(allSubscriptions, ...)` | `slices.SortFunc(allSubscriptions, ...)` | +| `pkg/account/subscriptions.go` | 84, 139, 168 | `sort.Slice(...)` | `slices.SortFunc(...)` | +| `internal/agent/tools/io/file_search.go` | 146 | `sort.Strings(secureMatches)` | `slices.Sort(secureMatches)` | +| `internal/telemetry/appinsights-exporter/transmitter.go` | 186 | `sort.Sort(result.Response.Errors)` | Keep — custom `sort.Interface` type | + +**Recommendation:** Replace `sort.Slice` → `slices.SortFunc` and `sort.Strings` → `slices.Sort` where the type +is a plain slice (not implementing `sort.Interface`). Use `cmp.Compare` for the comparison function. + +**Example transformation:** +```go +// Before +sort.Slice(allSubscriptions, func(i, j int) bool { + return allSubscriptions[i].Name < allSubscriptions[j].Name +}) + +// After +slices.SortFunc(allSubscriptions, func(a, b Subscription) int { + return cmp.Compare(a.Name, b.Name) +}) +``` + +**Impact:** Medium — improves type safety and readability, removes `sort` import in several files. + +--- + +## 2. `interface{}` → `any` + +**Count:** 140 total, but **133 are in generated `.pb.go` files** (protobuf/gRPC stubs). + +**Non-generated occurrences (7):** + +| File | Line | Code | +|---|---|---| +| `pkg/llm/github_copilot.go` | 205 | `func saveToFile(filePath string, data interface{}) error` | +| `pkg/llm/github_copilot.go` | 220 | `func loadFromFile(filePath string, data interface{}) error` | +| `pkg/llm/github_copilot.go` | 410 | `var copilotResp map[string]interface{}` | +| `pkg/azapi/deployments.go` | 339-340 | Comment: `interface{}` | +| `internal/grpcserver/project_service.go` | 420 | Comment: `interface{}` | +| `internal/grpcserver/project_service.go` | 714 | Comment: `interface{}` | + +**Recommendation:** Run `go fix ./...` which should auto-convert `interface{}` → `any`. The generated `.pb.go` +files are controlled by protobuf codegen and should be left as-is (they'll update when protos are regenerated). + +**Impact:** Low — cosmetic modernization. Only 4 actual code changes needed (3 in `github_copilot.go`, 1 in `deployments.go`). + +--- + +## 3. `math.Min(float64(...), float64(...))` → built-in `min()` + +**Count:** 1 + +| File | Line | Current | +|---|---|---| +| `pkg/account/subscriptions_manager.go` | 304 | `numWorkers := int(math.Min(float64(len(tenants)), float64(maxWorkers)))` | + +**Recommendation:** +```go +// Before +numWorkers := int(math.Min(float64(len(tenants)), float64(maxWorkers))) + +// After +numWorkers := min(len(tenants), maxWorkers) +``` + +**Impact:** Low — single occurrence, but a clean improvement. Removes the awkward float64 casting. + +--- + +## 4. `sync.Map` → Generic Typed Map + +**Count:** 13 usages across 8 files + +| File | Line | Field | +|---|---|---| +| `internal/cmd/add/add_select_ai.go` | 357 | `var sharedResults sync.Map` | +| `pkg/grpcbroker/message_broker.go` | 81-82 | `responseChans sync.Map`, `handlers sync.Map` | +| `pkg/auth/credential_providers.go` | 27 | `tenantCredentials sync.Map` | +| `pkg/devcentersdk/developer_client.go` | 38 | `cache sync.Map` | +| `pkg/ux/canvas.go` | 153 | `items sync.Map` | +| `pkg/ai/model_service.go` | 218, 304 | `var sharedResults sync.Map` | +| `pkg/containerapps/container_app.go` | 115-116 | `appsClientCache sync.Map`, `jobsClientCache sync.Map` | + +**Recommendation:** Consider creating a generic `SyncMap[K, V]` wrapper or using a third-party typed +concurrent map. Go 1.23+ doesn't add a generic `sync.Map` in stdlib, but a project-level utility would +eliminate `any` casts at every `Load`/`Store` call site. + +```go +// Utility type +type SyncMap[K comparable, V any] struct { + m sync.Map +} + +func (s *SyncMap[K, V]) Load(key K) (V, bool) { + v, ok := s.m.Load(key) + if !ok { + var zero V + return zero, false + } + return v.(V), true +} +``` + +**Impact:** Medium — removes type assertions at every call site, prevents type mismatch bugs. + +--- + +## 5. `http.NewRequest` → `http.NewRequestWithContext` + +**Count:** 6 non-test occurrences using `http.NewRequest` without context + +| File | Line | +|---|---| +| `tools/avmres/main.go` | 193 | +| `internal/telemetry/appinsights-exporter/transmitter.go` | 80 | +| `extensions/azure.ai.agents/internal/project/parser.go` | 1041, 1106, 1161 | +| `pkg/llm/github_copilot.go` | 388 | + +The codebase already uses `http.NewRequestWithContext` in 30 other places — these 6 are inconsistent. + +**Recommendation:** Replace all `http.NewRequest(...)` with `http.NewRequestWithContext(ctx, ...)` to ensure +proper cancellation propagation. If no context is available, use `context.Background()` explicitly. + +**Impact:** Medium — ensures cancellation and timeout propagation works correctly for HTTP calls. + +--- + +## 6. `sort.Strings` → `slices.Sort` + +**Count:** 2 non-test files + +| File | Line | Current | +|---|---|---| +| `internal/agent/tools/io/file_search.go` | 146 | `sort.Strings(secureMatches)` | +| `extensions/azure.ai.finetune/internal/cmd/validation.go` | 75 | `sort.Strings(missingFlags)` | + +**Recommendation:** `slices.Sort(secureMatches)` — direct 1:1 replacement, no behavior change. + +**Impact:** Low — trivial cleanup. + +--- + +## 7. Manual Map Keys Collection → `slices.Sorted(maps.Keys(...))` + +**Count:** 1 remaining instance (the codebase already uses the modern pattern in 6+ places) + +| File | Line | Current Pattern | +|---|---|---| +| `pkg/azdext/scope_detector.go` | 116-120 | Manual `keys := make([]string, 0, len(m)); for k := range m { keys = append(keys, k) }; slices.Sort(keys)` | + +**Recommendation:** +```go +// Before (5 lines) +keys := make([]string, 0, len(opts.CustomRules)) +for k := range opts.CustomRules { + keys = append(keys, k) +} +slices.Sort(keys) + +// After (1 line) +keys := slices.Sorted(maps.Keys(opts.CustomRules)) +``` + +**Impact:** Low — single occurrence, but a good example of the pattern the rest of the codebase already follows. + +--- + +## 8. C-style `for i := 0; i < n; i++` → `for i := range n` + +**Count:** 6 candidates + +| File | Line | Current | +|---|---|---| +| `pkg/password/generator_test.go` | 84 | `for i := 0; i < len(choices); i++` | +| `pkg/yamlnode/yamlnode.go` | 235, 297 | `for i := 0; i < len(s); i++` — character-by-character parsing | +| `pkg/apphost/eval.go` | 33 | `for i := 0; i < len(src); i++` — character parsing | +| `pkg/output/table.go` | 141 | `for i := 0; i < v.Len(); i++` — reflect.Value iteration | +| `cmd/extensions.go` | 56 | `for i := 0; i < len(namespaceParts)-1; i++` | + +**Recommendation:** Only convert simple iteration patterns. The character-parsing loops in `yamlnode.go` and +`eval.go` modify `i` inside the loop body (e.g., `i++` to skip chars), so they **cannot** use `range n`. + +Convertible: +- `pkg/password/generator_test.go:84` → `for i := range len(choices)` +- `cmd/extensions.go:56` → `for i := range len(namespaceParts)-1` + +Not convertible (loop variable modified inside body): +- `pkg/yamlnode/yamlnode.go:235,297` — skip +- `pkg/apphost/eval.go:33` — skip +- `pkg/output/table.go:141` — uses `reflect.Value.Len()`, fine to convert: `for i := range v.Len()` + +**Impact:** Low — readability improvement for simple cases. + +--- + +## 9. Manual Slice Clone → `slices.Clone` + +**Count:** 82 total matches for `append([]T{}, ...)`, but most are in generated `.pb.go` files or +are actually prepend patterns (building a new slice with a prefix element), not clones. + +**Actual clone candidate (1):** + +| File | Line | Current | +|---|---|---| +| `pkg/project/service_target_external.go` | 298 | `return append([]string{}, endpointsResp.Endpoints...), nil` | + +**Recommendation:** +```go +// Before +return append([]string{}, endpointsResp.Endpoints...), nil + +// After +return slices.Clone(endpointsResp.Endpoints), nil +``` + +Most other `append([]T{}, ...)` patterns are prepend operations (e.g., `append([]string{cmd}, args...)`), +which are correct as-is — `slices.Clone` wouldn't apply. + +**Impact:** Low — single real clone candidate. + +--- + +## 10. `log.Printf` / `log.Println` → Structured Logging + +**Count:** 309 non-test occurrences of `log.Print*` + +**Hotspot files:** + +| File | Count (approx) | +|---|---| +| `pkg/pipeline/pipeline_manager.go` | ~20+ | +| `pkg/tools/pack/pack.go` | ~10 | +| `pkg/tools/github/github.go` | ~10 | +| `pkg/cmdsubst/secretOrRandomPassword.go` | 2 | + +**Recommendation:** This is the **highest-volume** modernization opportunity. Consider adopting `log/slog` +for structured, leveled logging. However, this is a large-effort change that would require: + +1. Defining a project-wide `slog.Handler` configuration +2. Replacing all `log.Printf` calls with `slog.Info`, `slog.Debug`, etc. +3. Ensuring log levels map correctly (many current `log.Printf` calls are debug-level) + +The codebase already has an internal tracing/telemetry system (`internal/tracing/`), so `slog` adoption +should integrate with that. + +**Impact:** High value, but **high effort**. Best done as a dedicated initiative, not incremental cleanup. + +--- + +## 11. Context Patterns + +**Count:** ~90 context usage sites + +The codebase already uses modern context patterns well: +- `context.WithoutCancel` — used in `main.go` and tests (Go 1.21+) ✅ +- `context.WithCancel`, `context.WithTimeout` — standard usage ✅ +- `context.AfterFunc` — not used (Go 1.21+, niche utility) + +**No actionable items** — context usage is modern. + +--- + +## 12. Error Handling Patterns + +**Count:** 2,742 `errors.New` / `fmt.Errorf` occurrences; 12 `errors.Join` usages + +The project already uses `errors.Join` where multi-error aggregation is needed. Error wrapping with +`fmt.Errorf("...: %w", err)` is the standard pattern and is used correctly throughout. + +**No actionable items** — error handling follows Go best practices. + +--- + +## 13. File I/O Patterns + +**Count:** 0 `ioutil.*` usages ✅ + +The codebase has already migrated from `io/ioutil` (deprecated in Go 1.16) to `os.ReadFile`, +`os.WriteFile`, etc. No action needed. + +--- + +## 14. Iterator Patterns (`iter.Seq` / `iter.Seq2`) + +**Count:** 0 direct usages + +The `iter` package (Go 1.23+) is not used directly, though `slices.Collect(maps.Keys(...))` and +range-over-func are used implicitly via the `slices`/`maps` packages. + +**Recommendation:** No immediate action. Iterator patterns are most useful for custom collection types; +the codebase doesn't have strong candidates for custom iterators currently. + +--- + +## 15. JSON Handling + +**Count:** 309 `json.Marshal`/`Unmarshal`/`NewDecoder`/`NewEncoder` usages + +Standard `encoding/json` usage throughout. Go 1.24+ introduced `encoding/json/v2` as an experiment, +but it's not stable yet. + +**No actionable items.** + +--- + +## 16. Crypto/TLS + +**Count:** 13 usages + +TLS configuration in `pkg/httputil/util.go` correctly sets `MinVersion: tls.VersionTLS12`. Standard +crypto usage (`crypto/sha256`, `crypto/rand`) throughout. + +**No actionable items.** + +--- + +## Priority Recommendations + +### Quick Wins (< 1 hour each) + +1. **`math.Min` → built-in `min`** — 1 file, 1 line +2. **`sort.Strings` → `slices.Sort`** — 2 files +3. **Manual keys collect → `slices.Sorted(maps.Keys(...))`** — 1 file +4. **`interface{}` → `any`** via `go fix ./...` — automatic +5. **`slices.Clone` for actual clone** — 1 file + +### Medium Effort (1-4 hours) + +6. **`sort.Slice` → `slices.SortFunc`** — ~8 non-test files +7. **`http.NewRequest` → `http.NewRequestWithContext`** — 6 files +8. **Range-over-int conversions** — 2-3 convertible sites + +### Large Effort (multi-day initiative) + +9. **`sync.Map` → generic typed wrapper** — 8 files, requires design +10. **`log.Printf` → `slog` structured logging** — 309 call sites, requires architecture decision + +--- + +## Already Modern ✅ + +The codebase is **already well-modernized** in several areas: + +- **266 usages** of `slices.*` / `maps.*` packages +- `slices.Sorted(maps.Keys(...))` pattern used in 6+ places +- `maps.Clone`, `maps.Copy` used in pipeline and infra packages +- `slices.Contains`, `slices.ContainsFunc`, `slices.IndexFunc` used extensively +- `slices.DeleteFunc` used for filtering +- `context.WithoutCancel` used correctly +- `errors.Join` used for multi-error scenarios +- Zero `ioutil.*` usages (fully migrated) +- `any` used throughout (only 7 hand-written `interface{}` remain) diff --git a/tmp/go-1.26-modernization-analysis.md b/tmp/go-1.26-modernization-analysis.md new file mode 100644 index 00000000000..ce430dcdd3c --- /dev/null +++ b/tmp/go-1.26-modernization-analysis.md @@ -0,0 +1,623 @@ +# Go 1.26 Modernization Analysis — Azure Developer CLI + +**Date:** 2026-03-12 +**Go Version:** 1.26 (go1.26.1 darwin/arm64) +**Analyzed by:** GPT-5.4 (feature research) + Claude Opus 4.6 (codebase scan) +**Scope:** `cli/azd/` — pkg, cmd, internal + +--- + +## Executive Summary + +The azd codebase is **already well-modernized** — 266 usages of `slices`/`maps` packages, zero `ioutil`, and `errors.Join` in use. However, Go 1.24–1.26 introduced several impactful features the project hasn't adopted yet. This document maps new Go features to concrete azd opportunities, organized by impact and effort. + +### Top 5 Opportunities + +| # | Feature | Impact | Effort | Details | +|---|---------|--------|--------|---------| +| 1 | `errors.AsType[T]` (1.26) | 🟢 High | Low | Generic, type-safe error matching — eliminates boilerplate | +| 2 | `WaitGroup.Go` (1.25) | 🟢 High | Medium | Simplifies concurrent goroutine patterns across CLI | +| 3 | `omitzero` JSON tag (1.24) | 🟡 Medium | Low | Better zero-value omission for `time.Time` and optional fields | +| 4 | `os.Root` (1.24/1.25) | 🟡 Medium | Medium | Secure directory-scoped file access | +| 5 | `testing.T.Context` / `B.Loop` (1.24) | 🟡 Medium | Low | Modern test patterns | + +--- + +## Part 1: New Go 1.24–1.26 Features & azd Applicability + +### 🔤 Language Changes + +#### Generic Type Aliases (Go 1.24) +```go +// Now supported: +type Set[T comparable] = map[T]bool +type Result[T any] = struct { Value T; Err error } +``` +**azd applicability:** Could simplify type layering in `pkg/project/` and `pkg/infra/` where wrapper types are common. Low priority — most types are already well-defined. + +#### `new(expr)` — Initialize Pointer from Expression (Go 1.26) +```go +// Before +v := "hello" +ptr := &v + +// After +ptr := new("hello") +``` +**azd applicability:** Useful for optional pointer fields in protobuf/JSON structs. The codebase has several `to.Ptr()` helper patterns that this could partially replace. Medium value in new code. + +#### Self-Referential Generic Constraints (Go 1.26) +```go +type Adder[A Adder[A]] interface { Add(A) A } +``` +**azd applicability:** Niche — useful for F-bounded polymorphism patterns. No immediate need in azd. + +--- + +### ⚡ Performance Improvements (Free Wins) + +These improvements apply automatically with Go 1.26 — no code changes needed: + +| Feature | Source | Expected Impact | +|---------|--------|-----------------| +| **Green Tea GC** (default in 1.26) | Runtime | 10–40% GC overhead reduction | +| **Swiss Tables map impl** (1.24) | Runtime | 2–3% CPU reduction overall | +| **`io.ReadAll` optimization** (1.26) | stdlib | ~2x faster, lower memory | +| **cgo overhead -30%** (1.26) | Runtime | Faster cgo calls | +| **Stack-allocated slice backing** (1.25/1.26) | Compiler | Fewer heap allocations | +| **Container-aware GOMAXPROCS** (1.25) | Runtime | Better perf in containers | +| **`fmt.Errorf` for plain strings** (1.26) | stdlib | Less allocation, closer to `errors.New` | + +**Action:** Simply building with Go 1.26 gets these for free. Consider benchmarking before/after to quantify improvements for azd's specific workload. + +--- + +### 🛠️ Standard Library Additions to Adopt + +#### 1. `errors.AsType[T]` (Go 1.26) — **HIGH IMPACT** + +Generic, type-safe replacement for `errors.As`: + +```go +// Before (current azd pattern — verbose, requires pre-declaring variable) +var httpErr *azcore.ResponseError +if errors.As(err, &httpErr) { + // use httpErr +} + +// After (Go 1.26 — single expression, type-safe) +if httpErr, ok := errors.AsType[*azcore.ResponseError](err); ok { + // use httpErr +} +``` + +**azd occurrences:** The codebase has ~200+ `errors.As` calls. This is the single highest-impact modernization: +- Eliminates pre-declared error variables +- Type is inferred from the generic parameter +- Faster than `errors.As` in many cases + +**Files with heavy `errors.As` usage:** +- `pkg/azapi/` — Azure API error handling +- `pkg/infra/provisioning/` — deployment error classification +- `cmd/` — command error handling +- `internal/cmd/` — action error handling + +#### 2. `sync.WaitGroup.Go` (Go 1.25) — **HIGH IMPACT** + +```go +// Before (current azd pattern) +var wg sync.WaitGroup +for _, item := range items { + wg.Add(1) + go func() { + defer wg.Done() + process(item) + }() +} +wg.Wait() + +// After (Go 1.25) +var wg sync.WaitGroup +for _, item := range items { + wg.Go(func() { + process(item) + }) +} +wg.Wait() +``` + +**azd occurrences:** Multiple goroutine fan-out patterns across: +- `pkg/account/subscriptions_manager.go` — parallel tenant subscription fetching +- `pkg/project/` — parallel service operations +- `pkg/infra/` — parallel resource operations +- `internal/cmd/` — parallel deployment steps + +**Benefits:** Eliminates `Add(1)` + `defer Done()` boilerplate, reduces goroutine leak risk. + +#### 3. `omitzero` JSON Struct Tag (Go 1.24) — **MEDIUM IMPACT** + +```go +// Before — omitempty doesn't work well with time.Time (zero time is not "empty") +type Config struct { + CreatedAt time.Time `json:"createdAt,omitempty"` // BUG: zero time still serialized +} + +// After — omitzero uses IsZero() method +type Config struct { + CreatedAt time.Time `json:"createdAt,omitzero"` // Correct: omits zero time +} +``` + +**azd applicability:** Any struct with `time.Time` fields or custom types implementing `IsZero()`. Check `pkg/config/`, `pkg/project/`, `pkg/account/` for candidates. + +#### 4. `os.Root` / `os.OpenRoot` (Go 1.24, expanded 1.25) — **MEDIUM IMPACT** + +Secure, directory-scoped filesystem access that prevents path traversal: + +```go +root, err := os.OpenRoot("/app/workspace") +if err != nil { ... } +defer root.Close() + +// All operations are confined to /app/workspace +data, err := root.ReadFile("config.yaml") // OK +data, err := root.ReadFile("../../etc/passwd") // Error: escapes root +``` + +**Go 1.25 additions:** `Chmod`, `Chown`, `MkdirAll`, `ReadFile`, `RemoveAll`, `Rename`, `Symlink`, `WriteFile` + +**azd applicability:** +- `pkg/project/` — reading `azure.yaml` and project files +- `pkg/osutil/` — file utility operations +- Extension framework — sandboxing extension file access +- Any user-supplied path handling for security hardening + +#### 5. `testing.T.Context` / `testing.B.Loop` (Go 1.24) — **MEDIUM IMPACT** + +```go +// T.Context — automatic context tied to test lifecycle +func TestFoo(t *testing.T) { + ctx := t.Context() // cancelled when test ends + result, err := myService.Do(ctx) +} + +// B.Loop — cleaner benchmarks +func BenchmarkFoo(b *testing.B) { + for b.Loop() { // replaces for i := 0; i < b.N; i++ + doWork() + } +} +``` + +**azd applicability:** The test suite has 2000+ test functions. `T.Context()` would simplify context creation in tests that currently use `context.Background()` or `context.TODO()`. + +#### 6. `T.Chdir` (Go 1.24) + +```go +// Before +oldDir, _ := os.Getwd() +os.Chdir(tempDir) +defer os.Chdir(oldDir) + +// After +t.Chdir(tempDir) // auto-restored when test ends +``` + +**azd applicability:** Multiple test files change directory manually. `T.Chdir` is safer and cleaner. + +#### 7. `bytes.Buffer.Peek` (Go 1.26) + +Access buffered bytes without consuming them. Useful in streaming/parsing scenarios. + +#### 8. `testing/synctest` (Go 1.25 GA) + +Fake-time testing for concurrent code: + +```go +synctest.Run(func() { + go func() { + time.Sleep(time.Hour) // instant in fake time + ch <- result + }() + synctest.Wait() // wait for all goroutines in bubble +}) +``` + +**azd applicability:** Could simplify testing of timeout/retry logic in `pkg/retry/`, `pkg/httputil/`, and polling operations. + +#### 9. String/Bytes Iterator Helpers (Go 1.24) + +```go +// New iterator-returning functions +for line := range strings.Lines(text) { ... } +for part := range strings.SplitSeq(text, ",") { ... } +for field := range strings.FieldsSeq(text) { ... } +``` + +**azd applicability:** Can replace `strings.Split` + loop patterns where only iteration is needed (avoids allocating the intermediate slice). + +#### 10. `testing.T.Attr` / `T.ArtifactDir` (Go 1.25/1.26) + +Structured test metadata and artifact directories for richer test output. + +--- + +### 🔧 Tooling Changes to Leverage + +#### `go fix` Modernizers (Go 1.26) — **Use Now** + +The rewritten `go fix` tool includes automatic modernizers: +```bash +go fix ./... +``` +This will automatically: +- Convert `interface{}` → `any` +- Simplify loop patterns +- Apply other Go version-appropriate modernizations + +**Recommendation:** Run `go fix ./...` as a first pass before any manual changes. + +#### `tool` Directives in `go.mod` (Go 1.24) + +``` +// go.mod +tool ( + golang.org/x/tools/cmd/stringer + github.com/golangci/golangci-lint/cmd/golangci-lint +) +``` + +Replaces `tools.go` pattern for declaring tool dependencies. Pinned versions, cached execution. + +**azd applicability:** The project likely uses build tools that could be declared here. + +#### `go.mod` `ignore` Directive (Go 1.25) + +Ignore directories that shouldn't be part of the module: +``` +ignore vendor/legacy +``` + +**azd applicability:** Could be useful for the extensions directory structure. + +#### `//go:fix inline` (Go 1.26) + +Mark functions for source-level inlining by `go fix`: +```go +//go:fix inline +func deprecated() { newFunction() } +``` + +**azd applicability:** Useful when deprecating internal helper functions — `go fix` will inline callers automatically. + +--- + +## Part 2: Codebase Scan — Specific Modernization Opportunities + +### Quick Wins (< 1 hour total) + +#### A. `math.Min` → built-in `min()` (1 site) + +| File | Line | +|------|------| +| `pkg/account/subscriptions_manager.go` | 304 | + +```go +// Before +numWorkers := int(math.Min(float64(len(tenants)), float64(maxWorkers))) +// After +numWorkers := min(len(tenants), maxWorkers) +``` + +#### B. `sort.Strings` → `slices.Sort` (2 files) + +| File | Line | +|------|------| +| `internal/agent/tools/io/file_search.go` | 146 | +| `extensions/azure.ai.finetune/internal/cmd/validation.go` | 75 | + +#### C. Manual keys collect → `slices.Sorted(maps.Keys(...))` (1 file) + +| File | Line | +|------|------| +| `pkg/azdext/scope_detector.go` | 116-120 | + +```go +// Before (5 lines) +keys := make([]string, 0, len(opts.CustomRules)) +for k := range opts.CustomRules { keys = append(keys, k) } +slices.Sort(keys) +// After (1 line) +keys := slices.Sorted(maps.Keys(opts.CustomRules)) +``` + +#### D. Manual slice clone → `slices.Clone` (1 file) + +| File | Line | +|------|------| +| `pkg/project/service_target_external.go` | 298 | + +```go +// Before +return append([]string{}, endpointsResp.Endpoints...), nil +// After +return slices.Clone(endpointsResp.Endpoints), nil +``` + +#### E. Run `go fix ./...` (automatic) + +Converts remaining `interface{}` → `any` (7 hand-written occurrences) and applies other modernizations. + +--- + +### Medium Effort (1–4 hours each) + +#### F. `sort.Slice` → `slices.SortFunc` (~8 non-test files) + +| File | Pattern | +|------|---------| +| `pkg/infra/provisioning_progress_display.go:182` | `sort.Slice(newlyDeployedResources, ...)` | +| `pkg/account/subscriptions_manager.go:329` | `sort.Slice(allSubscriptions, ...)` | +| `pkg/account/subscriptions.go:84,139,168` | `sort.Slice(...)` | +| + 3 more files | — | + +```go +// Before +sort.Slice(items, func(i, j int) bool { return items[i].Name < items[j].Name }) +// After +slices.SortFunc(items, func(a, b Item) int { return cmp.Compare(a.Name, b.Name) }) +``` + +#### G. `http.NewRequest` → `http.NewRequestWithContext` (6 files) + +| File | +|------| +| `tools/avmres/main.go:193` | +| `internal/telemetry/appinsights-exporter/transmitter.go:80` | +| `extensions/azure.ai.agents/internal/project/parser.go:1041,1106,1161` | +| `pkg/llm/github_copilot.go:388` | + +#### H. Range-over-int conversions (2-3 sites) + +| File | Convertible? | +|------|-------------| +| `pkg/password/generator_test.go:84` | ✅ Yes | +| `cmd/extensions.go:56` | ✅ Yes | +| `pkg/output/table.go:141` | ✅ Yes (reflect) | +| `pkg/yamlnode/yamlnode.go:235,297` | ❌ No (modifies i) | +| `pkg/apphost/eval.go:33` | ❌ No (modifies i) | + +--- + +### Large Initiatives (Multi-day) + +#### I. `sync.Map` → Generic Typed Wrapper (13 usages, 8 files) + +Create a `SyncMap[K, V]` utility type to eliminate type assertions: + +```go +type SyncMap[K comparable, V any] struct { m sync.Map } + +func (s *SyncMap[K, V]) Load(key K) (V, bool) { ... } +func (s *SyncMap[K, V]) Store(key K, value V) { ... } +func (s *SyncMap[K, V]) Delete(key K) { ... } +func (s *SyncMap[K, V]) Range(f func(K, V) bool) { ... } +``` + +**Files to update:** +- `internal/cmd/add/add_select_ai.go` +- `pkg/grpcbroker/message_broker.go` +- `pkg/auth/credential_providers.go` +- `pkg/devcentersdk/developer_client.go` +- `pkg/ux/canvas.go` +- `pkg/ai/model_service.go` +- `pkg/containerapps/container_app.go` + +#### J. `log.Printf` → `slog` Structured Logging (309 sites) + +This is the **highest-volume opportunity** but requires an architecture decision: +- Define project-wide `slog.Handler` configuration +- Integrate with existing `internal/tracing/` system +- Map current `log.Printf` calls to appropriate `slog.Info`/`slog.Debug` levels +- Consider `--debug` flag integration + +**Recommendation:** Treat as a separate initiative with its own design doc. + +#### K. `errors.AsType` Migration (200+ sites) + +Systematic migration of `errors.As` → `errors.AsType[T]` across the codebase. + +**Recommendation:** Can be done incrementally — start with the highest-traffic error handling paths in `pkg/azapi/` and `pkg/infra/`. + +--- + +## Part 3: Testing Modernization + +### Current State +- **2000+ test functions** across the codebase +- Uses `testify/mock` for mocking +- Table-driven tests are the standard pattern + +### Opportunities + +| Feature | Where to Apply | Impact | +|---------|---------------|--------| +| `T.Context()` | Tests using `context.Background()` | Cleaner test lifecycle | +| `T.Chdir()` | Tests with manual `os.Chdir` | Safer directory changes | +| `B.Loop()` | Benchmarks using `for i := 0; i < b.N; i++` | Cleaner benchmarks | +| `testing/synctest` | Timeout/retry/polling tests | Deterministic timing | +| `T.ArtifactDir()` | Tests generating output files | Organized test artifacts | +| `testing/cryptotest.SetGlobalRandom` | Crypto tests needing determinism | Reproducible crypto tests | + +--- + +## Part 4: Security Improvements + +| Feature | Description | azd Relevance | +|---------|-------------|---------------| +| `os.Root` (1.24/1.25) | Path-traversal-safe file access | Project file reading, extension sandbox | +| `net/http.CrossOriginProtection` (1.25) | Built-in CSRF via Fetch Metadata | Local dev server callbacks | +| Post-quantum TLS (1.26) | `SecP256r1MLKEM768`, `SecP384r1MLKEM1024` | Automatic via Go runtime | +| RSA min 1024-bit (1.24) | Enforced by crypto/rsa | Automatic | +| Heap base randomization (1.26) | ASLR improvement | Automatic | + +--- + +## Part 5: PR-Sized Adoption Roadmap + +Each item below is scoped to a single, small PR that can be reviewed independently. + +--- + +### PR 1: `go fix` automated modernizations +**Files:** All `.go` files (auto-applied) +**Changes:** `interface{}` → `any`, loop simplifications, other `go fix` modernizers +**Review notes:** Pure mechanical transform — reviewer just confirms no behavioral changes +**Commands:** +```bash +go fix ./... +gofmt -s -w . +``` + +--- + +### PR 2: Built-in `min()` and `slices.Sort` quick wins +**Files:** 3 files, ~5 lines changed +**Changes:** +- `pkg/account/subscriptions_manager.go`: `math.Min(float64, float64)` → `min()` +- `internal/agent/tools/io/file_search.go`: `sort.Strings` → `slices.Sort` +- `extensions/azure.ai.finetune/internal/cmd/validation.go`: `sort.Strings` → `slices.Sort` + +--- + +### PR 3: `slices.Clone` and `slices.Sorted(maps.Keys(...))` one-liners +**Files:** 2 files, ~6 lines changed +**Changes:** +- `pkg/project/service_target_external.go`: `append([]T{}, s...)` → `slices.Clone(s)` +- `pkg/azdext/scope_detector.go`: manual keys collect + sort → `slices.Sorted(maps.Keys(...))` + +--- + +### PR 4: `sort.Slice` → `slices.SortFunc` in account package +**Files:** 2 files (~4 call sites) +**Changes:** +- `pkg/account/subscriptions_manager.go`: `sort.Slice` → `slices.SortFunc` with `cmp.Compare` +- `pkg/account/subscriptions.go`: 3 × `sort.Slice` → `slices.SortFunc` + +--- + +### PR 5: `sort.Slice` → `slices.SortFunc` in remaining packages +**Files:** ~4 files +**Changes:** +- `pkg/infra/provisioning_progress_display.go` +- Other non-test files with `sort.Slice` +- Does **not** touch `sort.Sort` on `sort.Interface` types (leave those as-is) + +--- + +### PR 6: `http.NewRequest` → `http.NewRequestWithContext` +**Files:** 6 files +**Changes:** +- `tools/avmres/main.go` +- `internal/telemetry/appinsights-exporter/transmitter.go` +- `extensions/azure.ai.agents/internal/project/parser.go` (3 sites) +- `pkg/llm/github_copilot.go` +- Thread existing `ctx` or use `context.Background()` explicitly + +--- + +### PR 7: Range-over-int modernization +**Files:** 3 files, ~3 lines each +**Changes:** +- `pkg/password/generator_test.go`: `for i := 0; i < len(choices); i++` → `for i := range len(choices)` +- `cmd/extensions.go`: similar +- `pkg/output/table.go`: `for i := 0; i < v.Len(); i++` → `for i := range v.Len()` +- Skips parser loops where `i` is modified inside the body + +--- + +### PR 8: Generic `SyncMap[K, V]` utility type +**Files:** 1 new file + 8 files updated +**Changes:** +- Create `pkg/sync/syncmap.go` with `SyncMap[K, V]` generic wrapper +- Update 8 files to use typed map instead of `sync.Map` + type assertions + +--- + +### PR 9: Adopt `errors.AsType[T]` — Azure API error paths +**Files:** `pkg/azapi/` (focused scope) +**Changes:** +- Replace `var errT *T; errors.As(err, &errT)` → `errT, ok := errors.AsType[*T](err)` +- Start with the highest-traffic error handling paths + +--- + +### PR 10: Adopt `errors.AsType[T]` — infra/provisioning +**Files:** `pkg/infra/provisioning/` and `pkg/infra/` +**Changes:** Same pattern as PR 9, scoped to infrastructure error handling + +--- + +### PR 11: Adopt `errors.AsType[T]` — commands and actions +**Files:** `cmd/` and `internal/cmd/` +**Changes:** Same pattern, scoped to command-level error handling + +--- + +### PR 12: Adopt `WaitGroup.Go` in concurrent patterns +**Files:** 4-6 files with goroutine fan-out +**Changes:** +- `pkg/account/subscriptions_manager.go`: parallel tenant fetching +- `pkg/project/`: parallel service operations +- Other files with `wg.Add(1); go func() { defer wg.Done(); ... }()` pattern +- Replace with `wg.Go(func() { ... })` + +--- + +### PR 13: Test modernization — `T.Context()` adoption (batch 1) +**Files:** ~20 test files (scoped to one package, e.g., `pkg/project/`) +**Changes:** +- Replace `context.Background()` / `context.TODO()` → `t.Context()` in tests +- One package at a time to keep PRs reviewable + +--- + +### PR 14: Test modernization — `T.Chdir()` adoption +**Files:** Test files with manual `os.Chdir` + defer restore +**Changes:** Replace with `t.Chdir(dir)` — auto-restored when test ends + +--- + +### Future PRs (require design discussion first) + +| PR | Topic | Notes | +|----|-------|-------| +| 15+ | `omitzero` JSON tags | Audit all JSON structs with `time.Time` fields | +| 16+ | `os.Root` secure file access | Needs design doc for project file reading | +| 17+ | `slog` structured logging | 309 sites — needs architecture decision | +| 18+ | `testing/synctest` for retry/timeout tests | Evaluate which tests benefit | +| 19+ | `tool` directives in `go.mod` | Replace `tools.go` pattern | + +--- + +## Appendix A: Already Modern ✅ + +The codebase already excels in these areas: +- **266 usages** of `slices.*` / `maps.*` packages +- `slices.Sorted(maps.Keys(...))` pattern in 6+ places +- `maps.Clone`, `maps.Copy` used correctly +- `context.WithoutCancel` used where appropriate +- `errors.Join` for multi-error aggregation +- Zero `ioutil.*` — fully migrated +- `any` used throughout (only 7 hand-written `interface{}` remain) + +## Appendix B: No Action Needed + +| Area | Status | +|------|--------| +| `ioutil` migration | ✅ Complete | +| `context.WithoutCancel` | ✅ Already used | +| `errors.Join` | ✅ Already used (12 sites) | +| `slices.Contains` | ✅ Used extensively | +| Crypto/TLS config | ✅ Modern (TLS 1.2 minimum) | +| File I/O patterns | ✅ Uses `os.ReadFile`/`os.WriteFile` | diff --git a/tmp/reports/azd-telemetry-28d-report.md b/tmp/reports/azd-telemetry-28d-report.md new file mode 100644 index 00000000000..1eceb672e7c --- /dev/null +++ b/tmp/reports/azd-telemetry-28d-report.md @@ -0,0 +1,342 @@ +# Azure Developer CLI (azd) — Rolling 28-Day Telemetry Report + +**Period:** Feb 21 – Mar 18, 2026 +**Source:** `ddazureclients.kusto.windows.net` / `DevCli` database +**Generated:** 2026-03-21 + +--- + +## Executive Summary + +| Metric | Value | +|--------|-------| +| **Total Executions** | 10,178,008 | +| **Unique Users** (DevDeviceId) | 129,003 | +| **Overall Success Rate** | 64.5% | +| **Unique Templates Used** | 2,533 | +| **Unique Azure Subscriptions** | 9,183 | +| **Unique Customer Orgs** | 3,857+ | + +### Monthly KPI Comparison (from AzdKPIs table) + +| KPI | Jan 2026 | Feb 2026 | Δ | +|-----|----------|----------|---| +| MAU (Active1d) | 16,636 | 18,811 | **+13.1%** | +| Engaged (2d) | 3,950 | 4,072 | +3.1% | +| Dedicated (10d) | 369 | 364 | -1.4% | +| Provisions | 59,152 | 63,881 | +8.0% | +| Deployments | 54,958 | 56,728 | +3.2% | +| Successful Provisions | 28,835 | 30,742 | +6.6% | +| Successful Deployments | 41,058 | 43,186 | +5.2% | +| Azure Subscriptions | 6,420 | 7,339 | **+14.3%** | + +--- + +## Daily Active Users Trend + +| Date | DAU | Executions | +|------|-----|-----------| +| Feb 21 (Fri) | 504 | 26,306 | +| Feb 22 (Sat) | 3,131 | 142,943 | +| Feb 23 (Sun) | 9,505 | 434,029 | +| Feb 24 (Mon) | 11,499 | 575,189 | +| Feb 25 (Tue) | 11,570 | 622,173 | +| Feb 26 (Wed) | 10,742 | 481,576 | +| Feb 27 (Thu) | 9,665 | 397,341 | +| Feb 28 (Fri) | 3,566 | 223,880 | +| Mar 1 (Sat) | 3,244 | 254,988 | +| Mar 2 (Sun) | 10,093 | 491,753 | +| Mar 3 (Mon) | 10,522 | 458,583 | +| Mar 4 (Tue) | 10,458 | 580,816 | +| Mar 5 (Wed) | 10,359 | 518,339 | +| Mar 6 (Thu) | 9,489 | 500,349 | +| Mar 7 (Fri) | 3,506 | 247,362 | +| Mar 8 (Sat) | 3,267 | 168,655 | +| Mar 9 (Sun) | 10,356 | 563,312 | +| Mar 10 (Mon) | 11,275 | 519,414 | +| Mar 11 (Tue) | 11,004 | 382,957 | +| Mar 12 (Wed) | 10,802 | 425,650 | +| Mar 13 (Thu) | 10,106 | 311,655 | +| Mar 14 (Fri) | 3,586 | 348,198 | +| Mar 15 (Sat) | 3,397 | 162,191 | +| Mar 16 (Sun) | 10,616 | 333,201 | +| Mar 17 (Mon) | 11,241 | 558,356 | +| Mar 18 (Tue) | 11,699 | 448,755 | + +> Weekday DAU averages ~10,500–11,500 users. Weekend dips to ~3,200–3,600. Pattern is healthy and consistent. + +--- + +## Top Commands (by executions) + +| Command | Executions | Unique Users | Success % | +|---------|-----------|-------------|-----------| +| `azd auth token` | 8,237,762 | 20,692 | 58.9% | +| `azd env set` | 428,612 | 33,758 | 99.5% | +| `azd auth login` | 208,951 | 72,188 | 88.3% | +| `azd env get-values` | 190,212 | 24,792 | 98.9% | +| `azd env get-value` | 173,396 | 10,748 | 84.3% | +| `azd env list` | 153,117 | 18,883 | 99.1% | +| **`azd provision`** | **127,640** | **40,469** | **58.8%** | +| **`azd deploy`** | **115,974** | **43,735** | **80.8%** | +| `azd package` | 76,273 | 20,476 | 92.8% | +| **`azd up`** | **71,276** | **11,110** | **31.0%** ⚠️ | +| `azd config set` | 62,626 | 40,166 | 100% | +| `azd env new` | 47,364 | 29,470 | 93.5% | +| `azd init` | 46,133 | 6,251 | 91.0% | +| `azd env select` | 39,631 | 26,229 | 84.2% | + +--- + +## Top Templates (by unique users) + +| Template | Users | Executions | +|----------|-------|-----------| +| **azd-init** (interactive init) | 8,427 | 81,078 | +| **azure-search-openai-demo** | 2,542 | 259,785 | +| **chat-with-your-data-solution-accelerator** | 625 | 5,707 | +| **multi-agent-custom-automation-engine** | 550 | 19,742 | +| **todo-nodejs-mongo** | 469 | 1,925 | +| **agentic-applications-for-unified-data-foundation** | 461 | 12,653 | + +--- + +## Execution Environments — IDEs & AI Agents + +### All Execution Environments + +| Environment | Executions | Unique Users | User Share | +|-------------|-----------|-------------|------------| +| **Desktop** (terminal) | 8,699,096 | 39,274 | 30.4% | +| **GitHub Actions** | 421,968 | 37,759 | 29.2% | +| **Azure Pipelines** | 358,116 | 32,543 | 25.2% | +| **VS Code** (extension) | 93,024 | 16,006 | 12.4% | +| **Visual Studio** | 76,789 | 4,886 | 3.8% | +| **Azure CloudShell** | 19,456 | 3,686 | 2.9% | +| **Claude Code** 🔥 | 367,193 | 699 | 0.5% | +| **GitHub Codespaces** | 36,097 | 1,066 | 0.8% | +| **GitLab CI** | 36,519 | 1,142 | 0.9% | +| **GitHub Copilot CLI** | 50,115 | 579 | 0.4% | +| **OpenCode** | 3,204 | 38 | — | +| **Gemini** | 102 | 12 | — | + +### 🤖 AI/LLM Agent Deep Dive + +#### Weekly Trend + +| Week | Claude Code Users | Claude Code Exec | Copilot CLI Users | Copilot CLI Exec | OpenCode Users | Gemini Users | +|------|------------------|-----------------|-------------------|-----------------|---------------|-------------| +| Feb 16 | 20 | 1,213 | 9 | 177 | 3 | — | +| Feb 23 | 202 | 28,918 | 185 | 12,362 | 17 | 6 | +| Mar 2 | 263 | 133,593 | 228 | 18,716 | 11 | 2 | +| Mar 9 | **263** | **159,470** | 218 | 11,015 | 14 | 2 | +| Mar 16 | 218 | 43,999 | 173 | 7,845 | 12 | 2 | + +#### Success Rates Over Time + +| Week | Claude Code | Copilot CLI | OpenCode | Gemini | +|------|------------|------------|----------|--------| +| Feb 16 | 23.1% | 79.1% | 75.0% | — | +| Feb 23 | 31.2% | 74.4% | 71.2% | 62.0% | +| Mar 2 | 46.4% | 48.5% | 24.5% | 69.2% | +| Mar 9 | **87.9%** | 69.5% | 66.0% | 80.0% | +| Mar 16 | 82.5% | 63.1% | 74.8% | 20.0% | + +#### Top Commands by AI Agent + +**Claude Code** (367K executions, 699 users): + +| Command | Executions | Users | +|---------|-----------|-------| +| `auth token` | 284,257 | 375 | +| `env list` | 63,530 | 171 | +| `env get-values` | 5,804 | 217 | +| `env set` | 3,859 | 186 | +| `deploy` | 2,920 | 192 | +| `provision` | 1,198 | 170 | +| `up` | 709 | 125 | + +**GitHub Copilot CLI** (50K executions, 579 users): + +| Command | Executions | Users | +|---------|-----------|-------| +| `auth token` | 18,303 | 319 | +| `env get-value` | 6,747 | 69 | +| `env set` | 4,140 | 203 | +| `env get-values` | 3,766 | 214 | +| `deploy` | 3,043 | 187 | +| `provision` | 2,957 | 208 | +| `up` | 1,903 | 172 | + +> Notable: Copilot CLI is also running **MCP operations** (`mcp.validate_azure_yaml`, `mcp.iac_generation_rules`, `mcp.infrastructure_generation`, etc.) — 70 total MCP calls from 19+ users. + +#### AI Agent Key Insights + +- 🔥 **Claude Code is exploding** — went from 20 users to 263 in 4 weeks, with 367K executions (more than Copilot CLI despite fewer users). Heavy `auth token` loop suggests agentic workflow patterns. +- **Claude Code success rate improved dramatically** — from 23% to 88% in 4 weeks, suggesting rapid integration maturation. +- **Copilot CLI** is steady at ~200 users/week with 50K executions. Already using azd's MCP tools. +- **AI agents collectively**: ~1,300 users, 420K executions — about **4% of total azd volume** and growing fast. + +--- + +## Customer Breakdown + +### By Segment + +| Segment | Customers | Users | Subscriptions | Executions | +|---------|-----------|-------|---------------|------------| +| **SMB Commercial** | 1,852 | 26,637 | 2,732 | 473K | +| **Unspecified** | 993 | 21,622 | 2,466 | 445K | +| **Upper Majors Commercial** | 336 | 11,325 | 588 | 83K | +| **Strategic Commercial** | 188 | 5,054 | 564 | 113K | +| **Corporate Commercial** | 234 | 3,370 | 822 | 41K | +| **Upper Majors Public Sector** | 118 | 2,434 | 175 | 19K | +| Majors Growth Commercial | 34 | 418 | 44 | 5K | +| Strategic Public Sector | 29 | 264 | 49 | 5K | + +### Top Customers (by unique users) + +| Customer | Segment | Country | Users | Subs | Executions | Success% | +|----------|---------|---------|-------|------|------------|----------| +| **CoreAI - Platform & Tools** | Internal | 🇺🇸 US | 9,246 | 35 | 73K | 86.9% | +| **Investec Bank** | Upper Majors | 🇬🇧 UK | 6,471 | 74 | 15K | 90.3% | +| **Microsoft** | Internal | 🇺🇸 US | 3,976 | 978 | 149K | 82.8% | +| **Cloud + AI** | Internal | 🇨🇳 CN | 3,066 | 183 | 107K | 96.3% | +| **Vee Friends** | SMB | 🇺🇸 US | 2,585 | 1 | 8K | 99.0% | +| **Grupo Cosan** | Upper Majors | 🇧🇷 BR | 1,667 | 1 | 5K | 100% | +| **Puerto Rico PRITS** | Public Sector | 🇵🇷 PR | 1,422 | 2 | 4K | 94.9% | +| **H&M** | Strategic | 🇸🇪 SE | 1,107 | 40 | 8K | 91.7% | +| **Jersey Telecoms** | Corporate | 🇬🇧 UK | 951 | 2 | 7K | 99.6% | +| **Volkswagen** | Strategic | 🇩🇪 DE | 664 | 17 | 39K | 98.6% | +| **Microsoft Security** | Internal | 🇺🇸 US | 640 | 15 | 14K | 95.7% | +| **IBM** | Strategic | 🇺🇸 US | 385 | 7 | 4K | 83.2% | +| **Deloitte** | Strategic | 🇺🇸 US | 329 | 10 | 1K | 96.3% | +| **bp** | Strategic | 🇬🇧 UK | 271 | 9 | 6K | 97.9% | +| **Allianz Technology** | Strategic | 🇩🇪 DE | 233 | 8 | 11K | 99.8% | +| **Rabobank** | Strategic | 🇳🇱 NL | 248 | 8 | 462 | 92.2% | +| **EY Global** | Strategic | 🇺🇸 US | 201 | 25 | 2K | 71.6% | +| **Mercedes-Benz** | Strategic | 🇩🇪 DE | 195 | 6 | 3K | 99.0% | + +--- + +## Error Analysis + +### Overall Failure Rate Trend + +| Week | Total Exec | Failures | Fail Rate | +|------|-----------|----------|-----------| +| Feb 16 | 168K | 87K | 52.0% | +| Feb 23 | 2,989K | 1,050K | 35.1% | +| Mar 2 | 2,966K | 1,015K | 34.2% | +| Mar 9 | 2,713K | 948K | 35.0% | +| Mar 16 | 1,340K | 513K | 38.3% | + +### Success Rates by Command + +| Command | Total | Failures | Success% | Users | +|---------|-------|----------|----------|-------| +| `auth token` | 8,236,682 | 3,384,478 | **58.9%** | 20,689 | +| `auth login` | 208,930 | 24,476 | 88.3% | 72,184 | +| `provision` | 127,630 | 52,639 | **58.8%** | 40,466 | +| `deploy` | 115,961 | 22,243 | 80.8% | 43,735 | +| `package` | 76,268 | 5,520 | 92.8% | 20,476 | +| **`up`** | **71,271** | **49,154** | **31.0%** ⚠️ | 11,110 | +| `env new` | 47,355 | 3,098 | 93.5% | 29,463 | +| `init` | 46,132 | 4,171 | 91.0% | 6,251 | +| `down` | 15,830 | 2,761 | 82.6% | 2,982 | +| `restore` | 4,979 | 1,993 | **60.0%** | 693 | + +### Top Result Codes (failure reasons) + +| Result Code | Count | Users | Category | +|-------------|-------|-------|----------| +| `UnknownError` | 2,383,049 | 8,128 | Uncategorized | +| `auth.login_required` | 547,007 | 1,984 | Auth | +| `internal.errors_errorString` | 462,344 | 16,978 | Internal | +| `auth.not_logged_in` | 66,331 | 1,769 | Auth | +| `error.suggestion` | 33,762 | 7,475 | User guidance | +| `service.arm.deployment.failed` | 22,033 | 4,025 | ARM | +| `service.aad.failed` | 13,619 | 1,276 | AAD | +| `internal.azidentity_AuthenticationFailedError` | 8,732 | 196 | Auth (identity) | +| `service.arm.validate.failed` | 6,426 | 1,220 | ARM | +| `service.arm.400` | 6,371 | 1,479 | ARM | +| `tool.bicep.failed` | 5,099 | 1,437 | Bicep | +| `tool.pwsh.failed` | 4,931 | 688 | PowerShell | +| `tool.docker.failed` | 4,655 | 1,024 | Docker | +| `tool.dotnet.failed` | 3,732 | 1,345 | .NET | +| `user.canceled` | 3,630 | 1,739 | User action | +| `tool.Docker.missing` | 2,936 | 784 | Docker not installed | +| `tool.terraform.failed` | 2,872 | 355 | Terraform | + +### Provision / Deploy / Up Error Breakdown + +| Command | Error Category | Result Code | Count | Users | +|---------|---------------|-------------|-------|-------| +| `provision` | ARM | arm.deployment.failed | 12,395 | 3,908 | +| `up` | ARM | error.suggestion | 7,036 | 1,928 | +| `up` | ARM | arm.deployment.failed | 4,725 | 1,466 | +| `provision` | ARM | arm.validate.failed | 3,393 | 1,208 | +| `provision` | ARM | arm.400 | 3,130 | 1,143 | +| `provision` | Bicep | bicep.failed | 2,715 | 1,311 | +| `up` | ARM | arm.400 | 2,540 | 940 | +| `provision` | PowerShell | pwsh.failed | 2,053 | 560 | +| `provision` | Terraform | terraform.failed | 1,873 | 335 | +| `deploy` | Docker | docker.failed | 1,652 | 692 | +| `up` | Docker | Docker.missing | 1,028 | 540 | +| `deploy` | .NET | dotnet.failed | 1,304 | 559 | + +### 🔍 `cmd.auth.token` Error Deep Dive + +`auth token` accounts for **3.38M of 3.61M total failures (94%)**. Breakdown: + +| Result Code | Error Category | Count | Users | % of Auth Token Failures | +|-------------|---------------|-------|-------|--------------------------| +| `UnknownError` | unknown | 2,352,993 | 6,403 | **69.5%** | +| `auth.login_required` | unknown | 543,972 | 1,353 | **16.1%** | +| `internal.errors_errorString` | unknown | 398,164 | 5,285 | **11.8%** | +| `auth.not_logged_in` | unknown | 65,643 | 1,233 | 1.9% | +| `service.aad.failed` | aad | 12,246 | 658 | 0.4% | +| `internal.azidentity_AuthenticationFailedError` | unknown | 7,258 | 65 | 0.2% | + +> ⚠️ **Classification gap:** 99.6% of auth token failures are categorized as "unknown" even when result codes like `auth.login_required` and `auth.not_logged_in` provide clear signals. Only `service.aad.failed` (12K) gets properly categorized. + +#### Auth Token Failure Rate by Execution Environment + +| Environment | Total | Failures | Fail % | +|-------------|-------|----------|--------| +| **Desktop** | 7,888,323 | 3,250,684 | **41.2%** | +| **Claude Code** | 284,257 | 116,750 | **41.1%** | +| **GitHub Copilot CLI** | 18,302 | 11,156 | **61.0%** ⚠️ | +| **GitHub Actions** | 12,915 | 383 | 3.0% ✅ | +| **GitHub Codespaces** | 10,580 | 1,417 | 13.4% | +| **Azure Pipelines** | 9,692 | 2,219 | 22.9% | +| **GitLab CI** | 5,147 | 0 | 0% ✅ | +| **OpenCode** | 2,053 | 908 | 44.2% | + +> CI/CD environments have near-zero auth token failures (service principal auth). Interactive/agent environments hit ~41-61% failure rates due to token expiry and "not logged in" retries. + +--- + +## Key Takeaways & Recommendations + +### 📈 Growth Signals +1. **MAU up 13% MoM**, Azure subscriptions up **14%** — strong growth trajectory +2. **AI agent adoption is hockey-sticking** — Claude Code went from 20→263 users in 4 weeks +3. **3,857+ customer organizations** actively using azd, with marquee enterprise logos (VW, H&M, bp, Mercedes, Allianz, IBM) + +### ⚠️ Areas of Concern +1. **`azd up` at 31% success rate** — the flagship command. Compounds provision + deploy failures. 49K failures affecting 11K users. +2. **`auth token` dominates failure volume** — 3.38M failures, but most are expected retry patterns (token refresh). Inflates overall failure rate from ~6% (excluding auth token) to ~35%. +3. **Error classification is broken** — 2.35M errors classified as `UnknownError` with no detail. `auth.login_required` (544K) is categorized as "unknown" despite having a clear result code. + +### 🔧 Suggested Actions +1. **Fix error categorization** — Map `auth.login_required`, `auth.not_logged_in`, and `azidentity_AuthenticationFailedError` result codes to an `auth` error category instead of `unknown`. +2. **Investigate `UnknownError`** (2.35M) — Add better error introspection to surface what's actually failing. +3. **Improve `azd up` reliability** — At 31% success, the compound command needs better pre-flight checks, clearer error messages, and possibly staged rollback. +4. **Address Docker-not-installed** (2,936 failures, 784 users) — Better pre-req detection and user guidance before attempting container deployments. +5. **Optimize for AI agents** — Claude Code and Copilot CLI are growing fast. Consider agent-specific auth flows (non-interactive token acquisition) and reducing the `auth token` retry loop noise. +6. **Monitor EY Global** — 71.6% success rate for a Strategic account is below the 90%+ benchmark seen at other enterprise customers. + +--- + +*Report generated from `AzdOperations`, `AzdKPIs`, and `Templates` tables on the `ddazureclients` Kusto cluster.* diff --git a/tmp/reports/azd-telemetry-opportunities-report.md b/tmp/reports/azd-telemetry-opportunities-report.md new file mode 100644 index 00000000000..6ca6f121e49 --- /dev/null +++ b/tmp/reports/azd-telemetry-opportunities-report.md @@ -0,0 +1,231 @@ +# azd Telemetry: Improvement Opportunities Report + +**Period:** Rolling 28 days (Feb 21 – Mar 18, 2026) +**Generated:** 2026-03-22 (updated 2026-03-23) +**Source:** Telemetry report + GitHub issue/PR cross-reference + +--- + +## Executive Summary + +azd processes **10.2M executions/28d** with a **64.5% overall success rate**. However, this headline number is misleading — `auth token` alone accounts for **3.38M of 3.61M failures (94%)**. Excluding auth token, the real command failure rate is closer to **~6%**. + +The three largest improvement opportunities are: + +| Opportunity | Volume Impact | Status | +|---|---|---| +| 1. **UnknownError bucket** | 2.35M errors with zero classification | 🟢 PR #7241 (in review) | +| 2. **Auth error misclassification** | 610K errors in wrong category | 🟢 PR #7235 (all CI green) | +| 3. **`azd up` at 31% success** | 49K failures, 11K users | 🟢 PR #7236 (auth pre-flight, all CI green) | + +--- + +## Opportunity 1: UnknownError — The Biggest Blind Spot + +### Scale +- **2,352,993 errors/28d** classified as `UnknownError` +- **6,403 users** affected +- **69.5%** of all `auth token` failures +- Currently provides **zero diagnostic signal** + +### Root Cause (confirmed) +`EndWithStatus(err)` in several high-volume code paths bypasses `MapError()` — the central error classification function. The telemetry converter then falls back to `"UnknownFailure"` when the span status description is empty or unrecognized. + +### Fix: PR #7241 (in review) +- MCP tool handler and Copilot agent spans now route through `MapError()` +- `EndWithStatus` fallback prefixed with `internal.` for consistency + +### Existing Work +| Issue/PR | Status | Coverage | +|---|---|---| +| **#7239** / PR #7241 | 🟢 PR open, CI green | Routes spans through MapError | +| #6796 "Reduce Unknown error bucket" | ✅ Closed (partial fix) | Addressed some categories | +| #6576 "Follow up on unknown errors classification" | ✅ Closed | Follow-up from #6796 | +| #5743 "Better unknown error breakdowns" | ✅ Closed | Added `errorType()` introspection | + +--- + +## Opportunity 2: Auth Error Classification ✅ IN PROGRESS + +### Scale +- **610K errors/28d** miscategorized as `"unknown"` instead of `"aad"` +- **~17%** of all auth token errors + +### Fix: PR #7235 (all CI green, awaiting review) +| Error | Before | After | +|---|---|---| +| `auth.login_required` (544K) | unknown ❌ | aad ✅ | +| `auth.not_logged_in` (66K) | unknown ❌ | aad ✅ | +| `azidentity.AuthenticationFailedError` (8.7K) | unknown ❌ | aad ✅ (new code: `auth.identity_failed`) | + +### Related Issues +| Issue | Status | Relationship | +|---|---|---| +| **#7233** | 🟢 PR #7235 open | Direct fix | +| #7104 "Revisit UserContextError categories" | 🟡 Open | Downstream — better error buckets for agents | + +--- + +## Opportunity 3: `azd up` Reliability (31% Success Rate) + +### Scale +- **49,154 failures/28d**, **11,110 users** +- The flagship compound command (provision + deploy) +- Failure cascades: a provision failure = `up` failure even if deploy would succeed + +### Error Breakdown for `azd up` +| Error Category | Count | Users | Preventable? | +|---|---|---|---| +| `error.suggestion` | 7,036 | 1,928 | ⚠️ Partially — many are auth errors wrapped in suggestions | +| `arm.deployment.failed` | 4,725 | 1,466 | ❌ Infrastructure errors (quota, policy, region) | +| `arm.400` | 2,540 | 940 | ⚠️ Some are preventable with validation | +| `Docker.missing` | 1,028 | 540 | ✅ **100% preventable** with pre-flight check | + +### Existing Work +| Issue/PR | Status | Coverage | +|---|---|---| +| **#7234** / PR #7236 | 🟢 PR open, CI green | Auth pre-flight + `--check` flag for agents | +| **#7240** Docker pre-flight | 🟡 Open (assigned spboyer) | Suggest remoteBuild when Docker missing | +| **#7179** Preflight: Azure Policy blocking local auth | 🟢 PR open (vhvb1989) | Detects policy-blocked deployments early | +| **#7115** Never abort deployment on validation errors | 🟡 Open | More resilient deploy behavior | +| **#3533** Verify dependency tools per service | 🟡 Open (good first issue) | Check only tools needed, not all | + +### Recommended Next Steps +1. **Ship #7234** (auth pre-flight middleware) — prevents late auth failures +2. **Ship #7240** (Docker pre-flight) — catch Docker/tool missing before starting +3. **Decompose `up` failure reporting** — show provision vs deploy failures separately so users know what succeeded +4. **Add ARM quota pre-flight** — check quota before deploying (prevents the most common ARM failures) + +--- + +## Opportunity 4: `internal.errors_errorString` — Untyped Errors + +### Scale +- **462,344 errors/28d**, **16,978 users** +- These are bare `errors.New()` calls without typed sentinels +- Each one is a classification opportunity lost + +### What's Happening +Code paths that return `errors.New("something failed")` or `fmt.Errorf("failed: %w", err)` where the inner error is also untyped end up in the catch-all bucket with the opaque name `errors_errorString`. + +### Recommended Next Steps +1. **Audit hot paths** — Find the top `errors.New()` call sites in auth token, provision, and deploy code paths +2. **Add typed sentinels progressively** — Start with the highest-volume error messages +3. **Test enforcement** — The test suite already has `allowedCatchAll` enforcement (errors_test.go line 791). Expand this pattern to prevent regressions. + +--- + +## Opportunity 5: AI Agent Optimization + +### Scale +- **420K executions/28d** from AI agents (~4% of total, growing fast) +- Claude Code: **367K executions**, 699 users (exploding growth) +- Copilot CLI: **50K executions**, 579 users (using MCP tools) + +### Key Pain Points +| Issue | Impact | Status | +|---|---|---| +| `auth token` failure rate: 61% (Copilot CLI), 41% (Claude Code) | Agents retry in loops, wasting cycles | 🟢 PR #7236 (`--check` flag) | +| No machine-readable auth status | Agents parse error messages | 🟢 PR #7236 (`expiresOn` in JSON) | +| Agent init errors lack remediation hints | Adoption barrier | 🟡 #6820 open | +| Extension compatibility issues | `env refresh` broken with agents ext | 🟡 #7195 open | + +### Existing Work +| Issue/PR | Status | Coverage | +|---|---|---| +| **#7234** / PR #7236 | 🟢 PR open, CI green | `azd auth token --check` + `expiresOn` in status | +| **#7202** Evaluation and testing framework | 🟢 PR open (spboyer) | Systematic testing for agent flows | +| **#6820** Agent init error remediation hints | 🟡 Open | Better error UX for agent setup | +| **#7195** `env refresh` + agent extension | 🟡 Open | Compatibility fix | +| **#7156** Hosted agent toolsets | 🟡 Open | Azure AI Foundry integration | + +--- + +## Opportunity 6: ARM Deployment Resilience + +### Scale +- **22,033 `arm.deployment.failed`/28d**, 4,025 users +- **6,426 `arm.validate.failed`/28d**, 1,220 users +- **6,371 `arm.400`/28d**, 1,479 users + +### Existing Work +| Issue/PR | Status | Coverage | +|---|---|---| +| **#6793** Investigate 500/503 ARM errors | 🟡 Open | ~707 preventable transient failures | +| **#7179** Preflight: Azure Policy detection | 🟢 PR open | Catches policy blocks before deploy | + +### Recommended Next Steps +1. **Add retry logic for transient 500/503** (#6793) — ~707 preventable failures +2. **Pre-flight quota check** — many ARM 400s are quota exceeded +3. **Surface inner deployment error codes** in user-facing messages + +--- + +## Opportunity 7: Tool Installation UX + +### Scale +- **Docker.missing**: 2,936 failures, 784 users +- **tool.docker.failed**: 4,655 failures, 1,024 users +- **tool.dotnet.failed**: 3,732 failures, 1,345 users +- **tool.bicep.failed**: 5,099 failures, 1,437 users + +### Existing Work +| Issue/PR | Status | Coverage | +|---|---|---| +| **#7240** Docker pre-flight + remoteBuild suggestion | 🟡 Open (assigned spboyer) | Detect upfront, suggest alternative | +| **#5715** Docker missing → suggest remoteBuild | 🟡 Open | Original request (superseded by #7240) | +| **#3533** Per-service tool validation | 🟡 Open (good first issue) | Only check needed tools | +| **#6424** Pre-tool-validation lifecycle event | 🟡 Open | Extension hook for guidance | + +--- + +## Deeper Questions To Investigate + +These are questions the current data could answer but the report doesn't address: + +### Metric Integrity +1. **Report two success rates** — Overall (64.5%) AND excluding auth token (~94%). The current headline is misleading; `auth token` is 80% of all executions and dominates every metric. + +### `azd up` Decomposition +2. **Stage-level failure attribution** — Where in the `up` flow does it fail? 70% at provision, 20% at deploy, 10% at both? Without this, we can't prioritize which stage to fix first. + +### Hidden Problem Commands +3. **`azd restore` at 60% success** — 1,993 failures, 693 users. A restore operation failing 40% of the time is a red flag nobody's investigating. +4. **`env get-value` at 84.3%** — A read-only key lookup failing 15.7% for 10,748 users. Likely missing env/key/project — but should be surfaced as a UX problem, not swallowed. +5. **`auth login` at 88.3%** — 24,476 login failures. Login should approach 100%. What's actually failing? + +### Template & Customer Analysis +6. **Template × command success matrix** — `azure-search-openai-demo` has 259K executions. If that template has a high failure rate, it skews numbers for 2,542 users. Need per-template success rates. +7. **Strategic account success variance** — EY at 71.6% vs Allianz at 99.8%. Same tool, 28-point gap. Understanding WHY (template? region? subscription policies?) enables targeted customer success. + +### Error Quality +8. **What's inside `error.suggestion`?** — 33,762 errors classified as `error.suggestion` but the inner error varies wildly. The `classifySuggestionType` inner code exists in telemetry — should be surfaced alongside the wrapper code. + +### User Journey & Retention +9. **End-to-end funnel** — `init` (91%) → `provision` (58.8%) → `deploy` (80.8%). What's the **cohort completion rate**? If a user inits, what % ever successfully deploy? +10. **First-run vs repeat failure rate** — Are tool failures (Docker, Bicep, dotnet) concentrated on first-run users or recurring? First-run = onboarding UX fix. Recurring = bug. +11. **Failure → churn correlation** — Engaged (2+ days) is only 4,072/18,811 MAU (21.6%). Are users who hit `azd up` failures coming back? If not, the 31% success rate is also a **retention problem**. + +### AI Agent Journey +12. **Agent → deployment conversion** — Agents call `auth token` heavily but how many get to `provision`/`deploy`? What's the agent user journey funnel vs desktop users? + +--- + +## Priority Matrix (Updated) + +| Priority | Opportunity | Volume | Effort | Status | +|---|---|---|---|---| +| 🟢 **In Review** | UnknownError classification | 2.35M | Medium | PR #7241 | +| 🟢 **In Review** | Auth error categories | 610K | Small | PR #7235 | +| 🟢 **In Review** | Auth pre-flight + agent `--check` | 610K cascade | Medium | PR #7236 | +| 🟡 **Filed** | Docker pre-flight | 2.9K + 4.7K | Small | #7240 | +| 🟡 **P1** | `azd up` decomposition | 49K | Medium | Needs Kusto query | +| 🟡 **P2** | `errors_errorString` audit | 462K | Large (ongoing) | No active issue | +| 🟡 **P2** | Template success matrix | Unknown | Small (query) | Needs Kusto query | +| 🟡 **P2** | `restore` / `env get-value` investigation | 4K | Small | No active issue | +| 🟡 **P2** | ARM resilience | 35K | Medium | #6793, #7179 | +| 🟢 **P3** | AI agent journey analysis | 420K | Small (query) | Needs Kusto query | + +--- + +*Cross-referenced against Azure/azure-dev open issues and PRs as of 2026-03-23.* diff --git a/tmp/reports/docker-missing-investigation.md b/tmp/reports/docker-missing-investigation.md new file mode 100644 index 00000000000..d73308a36a1 --- /dev/null +++ b/tmp/reports/docker-missing-investigation.md @@ -0,0 +1,101 @@ +# Docker Missing: Investigation & Implementation Plan + +**Generated:** 2026-03-22 +**Issue:** #5715, #3533 +**Telemetry:** 2,936 failures, 784 users / 28 days + +--- + +## Problem + +When Docker is not installed, `azd deploy`/`up` fails with `tool.Docker.missing` but **doesn't tell users they can use `remoteBuild: true`** — a built-in alternative that builds containers on Azure instead of locally. + +This affects Container Apps, AKS, and some Function App deployments where Docker is listed as required but isn't actually needed when remote builds are enabled. + +--- + +## Architecture + +``` +azd deploy + → projectManager.EnsureServiceTargetTools() + → containerHelper.RequiredExternalTools() + → if remoteBuild: true → [] (no Docker needed!) + → if remoteBuild: false/nil → [docker] (Docker required) + → tools.EnsureInstalled(docker) + → docker.CheckInstalled() → MissingToolErrors +``` + +### Key Insight + +`ContainerHelper.RequiredExternalTools()` (`pkg/project/container_helper.go:250-260`) already skips Docker when `remoteBuild: true`. The user just doesn't know this option exists. + +--- + +## What Supports remoteBuild + +| Service Target | remoteBuild Support | Default | Docker Required Without It | +|---|---|---|---| +| **Container Apps** | ✅ Yes | Not set | Yes | +| **AKS** | ✅ Yes | Not set | Yes | +| **Function App** (Flex) | ✅ Yes | JS/TS/Python: true | Varies | +| **App Service** | ❌ No | N/A | Yes (if containerized) | +| **Static Web App** | ❌ No | N/A | No | + +--- + +## Proposed Fix + +### Where to change: `internal/cmd/deploy.go` (after `EnsureServiceTargetTools`) + +When `MissingToolErrors` is caught with Docker in the tool list: + +1. Check which services actually need Docker (considering remoteBuild support) +2. If all services support remoteBuild → suggest it as primary alternative +3. If some do, some don't → suggest it for eligible services + install Docker for the rest + +### Error flow (proposed): + +```go +if toolErr, ok := errors.AsType[*tools.MissingToolErrors](err); ok { + if slices.Contains(toolErr.ToolNames, "Docker") { + // Check if services can use remoteBuild instead + return nil, &internal.ErrorWithSuggestion{ + Err: toolErr, + Suggestion: "Your services can build on Azure instead of locally. " + + "Add 'docker: { remoteBuild: true }' to each service in azure.yaml, " + + "or install Docker: https://aka.ms/azure-dev/docker-install", + } + } +} +``` + +### Files to modify + +| File | Change | +|---|---| +| `cli/azd/internal/cmd/deploy.go` | Catch Docker missing, wrap with remoteBuild suggestion | +| `cli/azd/pkg/project/project_manager.go` | Add helper to detect remoteBuild-capable services | +| `cli/azd/pkg/tools/docker/docker.go` | Optionally enhance base error message | +| `cli/azd/internal/cmd/deploy_test.go` | Test for suggestion when Docker missing | + +### Same pattern needed in: +- `internal/cmd/provision.go` (calls `EnsureAllTools`) +- `internal/cmd/up.go` (compound command) + +--- + +## Expected Impact + +- **2,936 failures/28d** get actionable guidance instead of a dead-end error +- **784 users** learn about remoteBuild as an alternative +- Users without Docker can still deploy to Container Apps and AKS +- Aligns with existing `ErrorWithSuggestion` pattern used throughout the codebase + +--- + +## Related Issues + +- **#5715** — "When docker is missing, suggest to use remoteBuild: true" (open) +- **#3533** — "Operations should verify presence of dependency tools needed for service" (open, good first issue) +- **#7239** — UnknownError investigation (newly created P0) From 31fc15e0a69fc871a07d1485df1de08855505ca8 Mon Sep 17 00:00:00 2001 From: Shayne Boyer Date: Mon, 23 Mar 2026 14:31:25 -0700 Subject: [PATCH 06/11] Remove tmp/ files from PR (not part of #7234) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tmp/codebase-scan-results.md | 395 ----------- tmp/go-1.26-modernization-analysis.md | 623 ------------------ tmp/reports/azd-telemetry-28d-report.md | 342 ---------- .../azd-telemetry-opportunities-report.md | 231 ------- tmp/reports/docker-missing-investigation.md | 101 --- 5 files changed, 1692 deletions(-) delete mode 100644 tmp/codebase-scan-results.md delete mode 100644 tmp/go-1.26-modernization-analysis.md delete mode 100644 tmp/reports/azd-telemetry-28d-report.md delete mode 100644 tmp/reports/azd-telemetry-opportunities-report.md delete mode 100644 tmp/reports/docker-missing-investigation.md diff --git a/tmp/codebase-scan-results.md b/tmp/codebase-scan-results.md deleted file mode 100644 index 9288cf2a641..00000000000 --- a/tmp/codebase-scan-results.md +++ /dev/null @@ -1,395 +0,0 @@ -# Go Modernization Analysis — Azure Developer CLI - -**Date:** 2025-07-22 -**Go Version:** 1.26 -**Branch:** `feat/local-fallback-remote-build` -**Scope:** `cli/azd/pkg/`, `cli/azd/cmd/`, `cli/azd/internal/` - ---- - -## Executive Summary - -The codebase already makes strong use of modern Go features: `slices`, `maps`, `slices.Sorted(maps.Keys(...))`, -`slices.Contains`, `maps.Clone`, `context.WithoutCancel`, and `errors.Join` are all present. The remaining -modernization opportunities are mostly **low-to-medium impact** incremental cleanups. - -| Category | Occurrences | Impact | Effort | -|---|---|---|---| -| 1. sort.Slice → slices.SortFunc | 16 | Medium | Low | -| 2. interface{} → any | 7 (non-generated) | Low | Low | -| 3. math.Min(float64) → built-in min | 1 | Low | Trivial | -| 4. sync.Map → generic typed map | 13 | Medium | Medium | -| 5. http.NewRequest → WithContext | 6 | Medium | Low | -| 6. sort.Strings → slices.Sort | 2 (non-test) | Low | Trivial | -| 7. Manual keys collect → slices.Sorted(maps.Keys) | 1 | Low | Trivial | -| 8. for i := 0; i < n; i++ → range n | 6 | Low | Trivial | -| 9. Manual slice clone → slices.Clone | 1 real case | Low | Trivial | -| 10. log.Printf → structured logging | 309 | High | High | -| 11. C-style for loops in parsers | 3 | Low | Low | - ---- - -## 1. `sort.Slice` / `sort.Sort` / `sort.Strings` → `slices` package - -**Count:** 16 total (10 in non-test code) - -The project already uses `slices.Sort`, `slices.SortFunc`, and `slices.Sorted(maps.Keys(...))` extensively -(~266 `slices.*` / `maps.*` usages). These 16 remaining `sort.*` calls are stragglers. - -**Non-test files to update:** - -| File | Line | Current | Replacement | -|---|---|---|---| -| `pkg/infra/provisioning_progress_display.go` | 182 | `sort.Slice(newlyDeployedResources, ...)` | `slices.SortFunc(newlyDeployedResources, ...)` | -| `pkg/extensions/manager.go` | 350 | `sort.Sort(semver.Collection(availableVersions))` | Keep — `semver.Collection` implements `sort.Interface`; no `slices` equivalent without wrapper | -| `pkg/account/subscriptions_manager.go` | 329 | `sort.Slice(allSubscriptions, ...)` | `slices.SortFunc(allSubscriptions, ...)` | -| `pkg/account/subscriptions.go` | 84, 139, 168 | `sort.Slice(...)` | `slices.SortFunc(...)` | -| `internal/agent/tools/io/file_search.go` | 146 | `sort.Strings(secureMatches)` | `slices.Sort(secureMatches)` | -| `internal/telemetry/appinsights-exporter/transmitter.go` | 186 | `sort.Sort(result.Response.Errors)` | Keep — custom `sort.Interface` type | - -**Recommendation:** Replace `sort.Slice` → `slices.SortFunc` and `sort.Strings` → `slices.Sort` where the type -is a plain slice (not implementing `sort.Interface`). Use `cmp.Compare` for the comparison function. - -**Example transformation:** -```go -// Before -sort.Slice(allSubscriptions, func(i, j int) bool { - return allSubscriptions[i].Name < allSubscriptions[j].Name -}) - -// After -slices.SortFunc(allSubscriptions, func(a, b Subscription) int { - return cmp.Compare(a.Name, b.Name) -}) -``` - -**Impact:** Medium — improves type safety and readability, removes `sort` import in several files. - ---- - -## 2. `interface{}` → `any` - -**Count:** 140 total, but **133 are in generated `.pb.go` files** (protobuf/gRPC stubs). - -**Non-generated occurrences (7):** - -| File | Line | Code | -|---|---|---| -| `pkg/llm/github_copilot.go` | 205 | `func saveToFile(filePath string, data interface{}) error` | -| `pkg/llm/github_copilot.go` | 220 | `func loadFromFile(filePath string, data interface{}) error` | -| `pkg/llm/github_copilot.go` | 410 | `var copilotResp map[string]interface{}` | -| `pkg/azapi/deployments.go` | 339-340 | Comment: `interface{}` | -| `internal/grpcserver/project_service.go` | 420 | Comment: `interface{}` | -| `internal/grpcserver/project_service.go` | 714 | Comment: `interface{}` | - -**Recommendation:** Run `go fix ./...` which should auto-convert `interface{}` → `any`. The generated `.pb.go` -files are controlled by protobuf codegen and should be left as-is (they'll update when protos are regenerated). - -**Impact:** Low — cosmetic modernization. Only 4 actual code changes needed (3 in `github_copilot.go`, 1 in `deployments.go`). - ---- - -## 3. `math.Min(float64(...), float64(...))` → built-in `min()` - -**Count:** 1 - -| File | Line | Current | -|---|---|---| -| `pkg/account/subscriptions_manager.go` | 304 | `numWorkers := int(math.Min(float64(len(tenants)), float64(maxWorkers)))` | - -**Recommendation:** -```go -// Before -numWorkers := int(math.Min(float64(len(tenants)), float64(maxWorkers))) - -// After -numWorkers := min(len(tenants), maxWorkers) -``` - -**Impact:** Low — single occurrence, but a clean improvement. Removes the awkward float64 casting. - ---- - -## 4. `sync.Map` → Generic Typed Map - -**Count:** 13 usages across 8 files - -| File | Line | Field | -|---|---|---| -| `internal/cmd/add/add_select_ai.go` | 357 | `var sharedResults sync.Map` | -| `pkg/grpcbroker/message_broker.go` | 81-82 | `responseChans sync.Map`, `handlers sync.Map` | -| `pkg/auth/credential_providers.go` | 27 | `tenantCredentials sync.Map` | -| `pkg/devcentersdk/developer_client.go` | 38 | `cache sync.Map` | -| `pkg/ux/canvas.go` | 153 | `items sync.Map` | -| `pkg/ai/model_service.go` | 218, 304 | `var sharedResults sync.Map` | -| `pkg/containerapps/container_app.go` | 115-116 | `appsClientCache sync.Map`, `jobsClientCache sync.Map` | - -**Recommendation:** Consider creating a generic `SyncMap[K, V]` wrapper or using a third-party typed -concurrent map. Go 1.23+ doesn't add a generic `sync.Map` in stdlib, but a project-level utility would -eliminate `any` casts at every `Load`/`Store` call site. - -```go -// Utility type -type SyncMap[K comparable, V any] struct { - m sync.Map -} - -func (s *SyncMap[K, V]) Load(key K) (V, bool) { - v, ok := s.m.Load(key) - if !ok { - var zero V - return zero, false - } - return v.(V), true -} -``` - -**Impact:** Medium — removes type assertions at every call site, prevents type mismatch bugs. - ---- - -## 5. `http.NewRequest` → `http.NewRequestWithContext` - -**Count:** 6 non-test occurrences using `http.NewRequest` without context - -| File | Line | -|---|---| -| `tools/avmres/main.go` | 193 | -| `internal/telemetry/appinsights-exporter/transmitter.go` | 80 | -| `extensions/azure.ai.agents/internal/project/parser.go` | 1041, 1106, 1161 | -| `pkg/llm/github_copilot.go` | 388 | - -The codebase already uses `http.NewRequestWithContext` in 30 other places — these 6 are inconsistent. - -**Recommendation:** Replace all `http.NewRequest(...)` with `http.NewRequestWithContext(ctx, ...)` to ensure -proper cancellation propagation. If no context is available, use `context.Background()` explicitly. - -**Impact:** Medium — ensures cancellation and timeout propagation works correctly for HTTP calls. - ---- - -## 6. `sort.Strings` → `slices.Sort` - -**Count:** 2 non-test files - -| File | Line | Current | -|---|---|---| -| `internal/agent/tools/io/file_search.go` | 146 | `sort.Strings(secureMatches)` | -| `extensions/azure.ai.finetune/internal/cmd/validation.go` | 75 | `sort.Strings(missingFlags)` | - -**Recommendation:** `slices.Sort(secureMatches)` — direct 1:1 replacement, no behavior change. - -**Impact:** Low — trivial cleanup. - ---- - -## 7. Manual Map Keys Collection → `slices.Sorted(maps.Keys(...))` - -**Count:** 1 remaining instance (the codebase already uses the modern pattern in 6+ places) - -| File | Line | Current Pattern | -|---|---|---| -| `pkg/azdext/scope_detector.go` | 116-120 | Manual `keys := make([]string, 0, len(m)); for k := range m { keys = append(keys, k) }; slices.Sort(keys)` | - -**Recommendation:** -```go -// Before (5 lines) -keys := make([]string, 0, len(opts.CustomRules)) -for k := range opts.CustomRules { - keys = append(keys, k) -} -slices.Sort(keys) - -// After (1 line) -keys := slices.Sorted(maps.Keys(opts.CustomRules)) -``` - -**Impact:** Low — single occurrence, but a good example of the pattern the rest of the codebase already follows. - ---- - -## 8. C-style `for i := 0; i < n; i++` → `for i := range n` - -**Count:** 6 candidates - -| File | Line | Current | -|---|---|---| -| `pkg/password/generator_test.go` | 84 | `for i := 0; i < len(choices); i++` | -| `pkg/yamlnode/yamlnode.go` | 235, 297 | `for i := 0; i < len(s); i++` — character-by-character parsing | -| `pkg/apphost/eval.go` | 33 | `for i := 0; i < len(src); i++` — character parsing | -| `pkg/output/table.go` | 141 | `for i := 0; i < v.Len(); i++` — reflect.Value iteration | -| `cmd/extensions.go` | 56 | `for i := 0; i < len(namespaceParts)-1; i++` | - -**Recommendation:** Only convert simple iteration patterns. The character-parsing loops in `yamlnode.go` and -`eval.go` modify `i` inside the loop body (e.g., `i++` to skip chars), so they **cannot** use `range n`. - -Convertible: -- `pkg/password/generator_test.go:84` → `for i := range len(choices)` -- `cmd/extensions.go:56` → `for i := range len(namespaceParts)-1` - -Not convertible (loop variable modified inside body): -- `pkg/yamlnode/yamlnode.go:235,297` — skip -- `pkg/apphost/eval.go:33` — skip -- `pkg/output/table.go:141` — uses `reflect.Value.Len()`, fine to convert: `for i := range v.Len()` - -**Impact:** Low — readability improvement for simple cases. - ---- - -## 9. Manual Slice Clone → `slices.Clone` - -**Count:** 82 total matches for `append([]T{}, ...)`, but most are in generated `.pb.go` files or -are actually prepend patterns (building a new slice with a prefix element), not clones. - -**Actual clone candidate (1):** - -| File | Line | Current | -|---|---|---| -| `pkg/project/service_target_external.go` | 298 | `return append([]string{}, endpointsResp.Endpoints...), nil` | - -**Recommendation:** -```go -// Before -return append([]string{}, endpointsResp.Endpoints...), nil - -// After -return slices.Clone(endpointsResp.Endpoints), nil -``` - -Most other `append([]T{}, ...)` patterns are prepend operations (e.g., `append([]string{cmd}, args...)`), -which are correct as-is — `slices.Clone` wouldn't apply. - -**Impact:** Low — single real clone candidate. - ---- - -## 10. `log.Printf` / `log.Println` → Structured Logging - -**Count:** 309 non-test occurrences of `log.Print*` - -**Hotspot files:** - -| File | Count (approx) | -|---|---| -| `pkg/pipeline/pipeline_manager.go` | ~20+ | -| `pkg/tools/pack/pack.go` | ~10 | -| `pkg/tools/github/github.go` | ~10 | -| `pkg/cmdsubst/secretOrRandomPassword.go` | 2 | - -**Recommendation:** This is the **highest-volume** modernization opportunity. Consider adopting `log/slog` -for structured, leveled logging. However, this is a large-effort change that would require: - -1. Defining a project-wide `slog.Handler` configuration -2. Replacing all `log.Printf` calls with `slog.Info`, `slog.Debug`, etc. -3. Ensuring log levels map correctly (many current `log.Printf` calls are debug-level) - -The codebase already has an internal tracing/telemetry system (`internal/tracing/`), so `slog` adoption -should integrate with that. - -**Impact:** High value, but **high effort**. Best done as a dedicated initiative, not incremental cleanup. - ---- - -## 11. Context Patterns - -**Count:** ~90 context usage sites - -The codebase already uses modern context patterns well: -- `context.WithoutCancel` — used in `main.go` and tests (Go 1.21+) ✅ -- `context.WithCancel`, `context.WithTimeout` — standard usage ✅ -- `context.AfterFunc` — not used (Go 1.21+, niche utility) - -**No actionable items** — context usage is modern. - ---- - -## 12. Error Handling Patterns - -**Count:** 2,742 `errors.New` / `fmt.Errorf` occurrences; 12 `errors.Join` usages - -The project already uses `errors.Join` where multi-error aggregation is needed. Error wrapping with -`fmt.Errorf("...: %w", err)` is the standard pattern and is used correctly throughout. - -**No actionable items** — error handling follows Go best practices. - ---- - -## 13. File I/O Patterns - -**Count:** 0 `ioutil.*` usages ✅ - -The codebase has already migrated from `io/ioutil` (deprecated in Go 1.16) to `os.ReadFile`, -`os.WriteFile`, etc. No action needed. - ---- - -## 14. Iterator Patterns (`iter.Seq` / `iter.Seq2`) - -**Count:** 0 direct usages - -The `iter` package (Go 1.23+) is not used directly, though `slices.Collect(maps.Keys(...))` and -range-over-func are used implicitly via the `slices`/`maps` packages. - -**Recommendation:** No immediate action. Iterator patterns are most useful for custom collection types; -the codebase doesn't have strong candidates for custom iterators currently. - ---- - -## 15. JSON Handling - -**Count:** 309 `json.Marshal`/`Unmarshal`/`NewDecoder`/`NewEncoder` usages - -Standard `encoding/json` usage throughout. Go 1.24+ introduced `encoding/json/v2` as an experiment, -but it's not stable yet. - -**No actionable items.** - ---- - -## 16. Crypto/TLS - -**Count:** 13 usages - -TLS configuration in `pkg/httputil/util.go` correctly sets `MinVersion: tls.VersionTLS12`. Standard -crypto usage (`crypto/sha256`, `crypto/rand`) throughout. - -**No actionable items.** - ---- - -## Priority Recommendations - -### Quick Wins (< 1 hour each) - -1. **`math.Min` → built-in `min`** — 1 file, 1 line -2. **`sort.Strings` → `slices.Sort`** — 2 files -3. **Manual keys collect → `slices.Sorted(maps.Keys(...))`** — 1 file -4. **`interface{}` → `any`** via `go fix ./...` — automatic -5. **`slices.Clone` for actual clone** — 1 file - -### Medium Effort (1-4 hours) - -6. **`sort.Slice` → `slices.SortFunc`** — ~8 non-test files -7. **`http.NewRequest` → `http.NewRequestWithContext`** — 6 files -8. **Range-over-int conversions** — 2-3 convertible sites - -### Large Effort (multi-day initiative) - -9. **`sync.Map` → generic typed wrapper** — 8 files, requires design -10. **`log.Printf` → `slog` structured logging** — 309 call sites, requires architecture decision - ---- - -## Already Modern ✅ - -The codebase is **already well-modernized** in several areas: - -- **266 usages** of `slices.*` / `maps.*` packages -- `slices.Sorted(maps.Keys(...))` pattern used in 6+ places -- `maps.Clone`, `maps.Copy` used in pipeline and infra packages -- `slices.Contains`, `slices.ContainsFunc`, `slices.IndexFunc` used extensively -- `slices.DeleteFunc` used for filtering -- `context.WithoutCancel` used correctly -- `errors.Join` used for multi-error scenarios -- Zero `ioutil.*` usages (fully migrated) -- `any` used throughout (only 7 hand-written `interface{}` remain) diff --git a/tmp/go-1.26-modernization-analysis.md b/tmp/go-1.26-modernization-analysis.md deleted file mode 100644 index ce430dcdd3c..00000000000 --- a/tmp/go-1.26-modernization-analysis.md +++ /dev/null @@ -1,623 +0,0 @@ -# Go 1.26 Modernization Analysis — Azure Developer CLI - -**Date:** 2026-03-12 -**Go Version:** 1.26 (go1.26.1 darwin/arm64) -**Analyzed by:** GPT-5.4 (feature research) + Claude Opus 4.6 (codebase scan) -**Scope:** `cli/azd/` — pkg, cmd, internal - ---- - -## Executive Summary - -The azd codebase is **already well-modernized** — 266 usages of `slices`/`maps` packages, zero `ioutil`, and `errors.Join` in use. However, Go 1.24–1.26 introduced several impactful features the project hasn't adopted yet. This document maps new Go features to concrete azd opportunities, organized by impact and effort. - -### Top 5 Opportunities - -| # | Feature | Impact | Effort | Details | -|---|---------|--------|--------|---------| -| 1 | `errors.AsType[T]` (1.26) | 🟢 High | Low | Generic, type-safe error matching — eliminates boilerplate | -| 2 | `WaitGroup.Go` (1.25) | 🟢 High | Medium | Simplifies concurrent goroutine patterns across CLI | -| 3 | `omitzero` JSON tag (1.24) | 🟡 Medium | Low | Better zero-value omission for `time.Time` and optional fields | -| 4 | `os.Root` (1.24/1.25) | 🟡 Medium | Medium | Secure directory-scoped file access | -| 5 | `testing.T.Context` / `B.Loop` (1.24) | 🟡 Medium | Low | Modern test patterns | - ---- - -## Part 1: New Go 1.24–1.26 Features & azd Applicability - -### 🔤 Language Changes - -#### Generic Type Aliases (Go 1.24) -```go -// Now supported: -type Set[T comparable] = map[T]bool -type Result[T any] = struct { Value T; Err error } -``` -**azd applicability:** Could simplify type layering in `pkg/project/` and `pkg/infra/` where wrapper types are common. Low priority — most types are already well-defined. - -#### `new(expr)` — Initialize Pointer from Expression (Go 1.26) -```go -// Before -v := "hello" -ptr := &v - -// After -ptr := new("hello") -``` -**azd applicability:** Useful for optional pointer fields in protobuf/JSON structs. The codebase has several `to.Ptr()` helper patterns that this could partially replace. Medium value in new code. - -#### Self-Referential Generic Constraints (Go 1.26) -```go -type Adder[A Adder[A]] interface { Add(A) A } -``` -**azd applicability:** Niche — useful for F-bounded polymorphism patterns. No immediate need in azd. - ---- - -### ⚡ Performance Improvements (Free Wins) - -These improvements apply automatically with Go 1.26 — no code changes needed: - -| Feature | Source | Expected Impact | -|---------|--------|-----------------| -| **Green Tea GC** (default in 1.26) | Runtime | 10–40% GC overhead reduction | -| **Swiss Tables map impl** (1.24) | Runtime | 2–3% CPU reduction overall | -| **`io.ReadAll` optimization** (1.26) | stdlib | ~2x faster, lower memory | -| **cgo overhead -30%** (1.26) | Runtime | Faster cgo calls | -| **Stack-allocated slice backing** (1.25/1.26) | Compiler | Fewer heap allocations | -| **Container-aware GOMAXPROCS** (1.25) | Runtime | Better perf in containers | -| **`fmt.Errorf` for plain strings** (1.26) | stdlib | Less allocation, closer to `errors.New` | - -**Action:** Simply building with Go 1.26 gets these for free. Consider benchmarking before/after to quantify improvements for azd's specific workload. - ---- - -### 🛠️ Standard Library Additions to Adopt - -#### 1. `errors.AsType[T]` (Go 1.26) — **HIGH IMPACT** - -Generic, type-safe replacement for `errors.As`: - -```go -// Before (current azd pattern — verbose, requires pre-declaring variable) -var httpErr *azcore.ResponseError -if errors.As(err, &httpErr) { - // use httpErr -} - -// After (Go 1.26 — single expression, type-safe) -if httpErr, ok := errors.AsType[*azcore.ResponseError](err); ok { - // use httpErr -} -``` - -**azd occurrences:** The codebase has ~200+ `errors.As` calls. This is the single highest-impact modernization: -- Eliminates pre-declared error variables -- Type is inferred from the generic parameter -- Faster than `errors.As` in many cases - -**Files with heavy `errors.As` usage:** -- `pkg/azapi/` — Azure API error handling -- `pkg/infra/provisioning/` — deployment error classification -- `cmd/` — command error handling -- `internal/cmd/` — action error handling - -#### 2. `sync.WaitGroup.Go` (Go 1.25) — **HIGH IMPACT** - -```go -// Before (current azd pattern) -var wg sync.WaitGroup -for _, item := range items { - wg.Add(1) - go func() { - defer wg.Done() - process(item) - }() -} -wg.Wait() - -// After (Go 1.25) -var wg sync.WaitGroup -for _, item := range items { - wg.Go(func() { - process(item) - }) -} -wg.Wait() -``` - -**azd occurrences:** Multiple goroutine fan-out patterns across: -- `pkg/account/subscriptions_manager.go` — parallel tenant subscription fetching -- `pkg/project/` — parallel service operations -- `pkg/infra/` — parallel resource operations -- `internal/cmd/` — parallel deployment steps - -**Benefits:** Eliminates `Add(1)` + `defer Done()` boilerplate, reduces goroutine leak risk. - -#### 3. `omitzero` JSON Struct Tag (Go 1.24) — **MEDIUM IMPACT** - -```go -// Before — omitempty doesn't work well with time.Time (zero time is not "empty") -type Config struct { - CreatedAt time.Time `json:"createdAt,omitempty"` // BUG: zero time still serialized -} - -// After — omitzero uses IsZero() method -type Config struct { - CreatedAt time.Time `json:"createdAt,omitzero"` // Correct: omits zero time -} -``` - -**azd applicability:** Any struct with `time.Time` fields or custom types implementing `IsZero()`. Check `pkg/config/`, `pkg/project/`, `pkg/account/` for candidates. - -#### 4. `os.Root` / `os.OpenRoot` (Go 1.24, expanded 1.25) — **MEDIUM IMPACT** - -Secure, directory-scoped filesystem access that prevents path traversal: - -```go -root, err := os.OpenRoot("/app/workspace") -if err != nil { ... } -defer root.Close() - -// All operations are confined to /app/workspace -data, err := root.ReadFile("config.yaml") // OK -data, err := root.ReadFile("../../etc/passwd") // Error: escapes root -``` - -**Go 1.25 additions:** `Chmod`, `Chown`, `MkdirAll`, `ReadFile`, `RemoveAll`, `Rename`, `Symlink`, `WriteFile` - -**azd applicability:** -- `pkg/project/` — reading `azure.yaml` and project files -- `pkg/osutil/` — file utility operations -- Extension framework — sandboxing extension file access -- Any user-supplied path handling for security hardening - -#### 5. `testing.T.Context` / `testing.B.Loop` (Go 1.24) — **MEDIUM IMPACT** - -```go -// T.Context — automatic context tied to test lifecycle -func TestFoo(t *testing.T) { - ctx := t.Context() // cancelled when test ends - result, err := myService.Do(ctx) -} - -// B.Loop — cleaner benchmarks -func BenchmarkFoo(b *testing.B) { - for b.Loop() { // replaces for i := 0; i < b.N; i++ - doWork() - } -} -``` - -**azd applicability:** The test suite has 2000+ test functions. `T.Context()` would simplify context creation in tests that currently use `context.Background()` or `context.TODO()`. - -#### 6. `T.Chdir` (Go 1.24) - -```go -// Before -oldDir, _ := os.Getwd() -os.Chdir(tempDir) -defer os.Chdir(oldDir) - -// After -t.Chdir(tempDir) // auto-restored when test ends -``` - -**azd applicability:** Multiple test files change directory manually. `T.Chdir` is safer and cleaner. - -#### 7. `bytes.Buffer.Peek` (Go 1.26) - -Access buffered bytes without consuming them. Useful in streaming/parsing scenarios. - -#### 8. `testing/synctest` (Go 1.25 GA) - -Fake-time testing for concurrent code: - -```go -synctest.Run(func() { - go func() { - time.Sleep(time.Hour) // instant in fake time - ch <- result - }() - synctest.Wait() // wait for all goroutines in bubble -}) -``` - -**azd applicability:** Could simplify testing of timeout/retry logic in `pkg/retry/`, `pkg/httputil/`, and polling operations. - -#### 9. String/Bytes Iterator Helpers (Go 1.24) - -```go -// New iterator-returning functions -for line := range strings.Lines(text) { ... } -for part := range strings.SplitSeq(text, ",") { ... } -for field := range strings.FieldsSeq(text) { ... } -``` - -**azd applicability:** Can replace `strings.Split` + loop patterns where only iteration is needed (avoids allocating the intermediate slice). - -#### 10. `testing.T.Attr` / `T.ArtifactDir` (Go 1.25/1.26) - -Structured test metadata and artifact directories for richer test output. - ---- - -### 🔧 Tooling Changes to Leverage - -#### `go fix` Modernizers (Go 1.26) — **Use Now** - -The rewritten `go fix` tool includes automatic modernizers: -```bash -go fix ./... -``` -This will automatically: -- Convert `interface{}` → `any` -- Simplify loop patterns -- Apply other Go version-appropriate modernizations - -**Recommendation:** Run `go fix ./...` as a first pass before any manual changes. - -#### `tool` Directives in `go.mod` (Go 1.24) - -``` -// go.mod -tool ( - golang.org/x/tools/cmd/stringer - github.com/golangci/golangci-lint/cmd/golangci-lint -) -``` - -Replaces `tools.go` pattern for declaring tool dependencies. Pinned versions, cached execution. - -**azd applicability:** The project likely uses build tools that could be declared here. - -#### `go.mod` `ignore` Directive (Go 1.25) - -Ignore directories that shouldn't be part of the module: -``` -ignore vendor/legacy -``` - -**azd applicability:** Could be useful for the extensions directory structure. - -#### `//go:fix inline` (Go 1.26) - -Mark functions for source-level inlining by `go fix`: -```go -//go:fix inline -func deprecated() { newFunction() } -``` - -**azd applicability:** Useful when deprecating internal helper functions — `go fix` will inline callers automatically. - ---- - -## Part 2: Codebase Scan — Specific Modernization Opportunities - -### Quick Wins (< 1 hour total) - -#### A. `math.Min` → built-in `min()` (1 site) - -| File | Line | -|------|------| -| `pkg/account/subscriptions_manager.go` | 304 | - -```go -// Before -numWorkers := int(math.Min(float64(len(tenants)), float64(maxWorkers))) -// After -numWorkers := min(len(tenants), maxWorkers) -``` - -#### B. `sort.Strings` → `slices.Sort` (2 files) - -| File | Line | -|------|------| -| `internal/agent/tools/io/file_search.go` | 146 | -| `extensions/azure.ai.finetune/internal/cmd/validation.go` | 75 | - -#### C. Manual keys collect → `slices.Sorted(maps.Keys(...))` (1 file) - -| File | Line | -|------|------| -| `pkg/azdext/scope_detector.go` | 116-120 | - -```go -// Before (5 lines) -keys := make([]string, 0, len(opts.CustomRules)) -for k := range opts.CustomRules { keys = append(keys, k) } -slices.Sort(keys) -// After (1 line) -keys := slices.Sorted(maps.Keys(opts.CustomRules)) -``` - -#### D. Manual slice clone → `slices.Clone` (1 file) - -| File | Line | -|------|------| -| `pkg/project/service_target_external.go` | 298 | - -```go -// Before -return append([]string{}, endpointsResp.Endpoints...), nil -// After -return slices.Clone(endpointsResp.Endpoints), nil -``` - -#### E. Run `go fix ./...` (automatic) - -Converts remaining `interface{}` → `any` (7 hand-written occurrences) and applies other modernizations. - ---- - -### Medium Effort (1–4 hours each) - -#### F. `sort.Slice` → `slices.SortFunc` (~8 non-test files) - -| File | Pattern | -|------|---------| -| `pkg/infra/provisioning_progress_display.go:182` | `sort.Slice(newlyDeployedResources, ...)` | -| `pkg/account/subscriptions_manager.go:329` | `sort.Slice(allSubscriptions, ...)` | -| `pkg/account/subscriptions.go:84,139,168` | `sort.Slice(...)` | -| + 3 more files | — | - -```go -// Before -sort.Slice(items, func(i, j int) bool { return items[i].Name < items[j].Name }) -// After -slices.SortFunc(items, func(a, b Item) int { return cmp.Compare(a.Name, b.Name) }) -``` - -#### G. `http.NewRequest` → `http.NewRequestWithContext` (6 files) - -| File | -|------| -| `tools/avmres/main.go:193` | -| `internal/telemetry/appinsights-exporter/transmitter.go:80` | -| `extensions/azure.ai.agents/internal/project/parser.go:1041,1106,1161` | -| `pkg/llm/github_copilot.go:388` | - -#### H. Range-over-int conversions (2-3 sites) - -| File | Convertible? | -|------|-------------| -| `pkg/password/generator_test.go:84` | ✅ Yes | -| `cmd/extensions.go:56` | ✅ Yes | -| `pkg/output/table.go:141` | ✅ Yes (reflect) | -| `pkg/yamlnode/yamlnode.go:235,297` | ❌ No (modifies i) | -| `pkg/apphost/eval.go:33` | ❌ No (modifies i) | - ---- - -### Large Initiatives (Multi-day) - -#### I. `sync.Map` → Generic Typed Wrapper (13 usages, 8 files) - -Create a `SyncMap[K, V]` utility type to eliminate type assertions: - -```go -type SyncMap[K comparable, V any] struct { m sync.Map } - -func (s *SyncMap[K, V]) Load(key K) (V, bool) { ... } -func (s *SyncMap[K, V]) Store(key K, value V) { ... } -func (s *SyncMap[K, V]) Delete(key K) { ... } -func (s *SyncMap[K, V]) Range(f func(K, V) bool) { ... } -``` - -**Files to update:** -- `internal/cmd/add/add_select_ai.go` -- `pkg/grpcbroker/message_broker.go` -- `pkg/auth/credential_providers.go` -- `pkg/devcentersdk/developer_client.go` -- `pkg/ux/canvas.go` -- `pkg/ai/model_service.go` -- `pkg/containerapps/container_app.go` - -#### J. `log.Printf` → `slog` Structured Logging (309 sites) - -This is the **highest-volume opportunity** but requires an architecture decision: -- Define project-wide `slog.Handler` configuration -- Integrate with existing `internal/tracing/` system -- Map current `log.Printf` calls to appropriate `slog.Info`/`slog.Debug` levels -- Consider `--debug` flag integration - -**Recommendation:** Treat as a separate initiative with its own design doc. - -#### K. `errors.AsType` Migration (200+ sites) - -Systematic migration of `errors.As` → `errors.AsType[T]` across the codebase. - -**Recommendation:** Can be done incrementally — start with the highest-traffic error handling paths in `pkg/azapi/` and `pkg/infra/`. - ---- - -## Part 3: Testing Modernization - -### Current State -- **2000+ test functions** across the codebase -- Uses `testify/mock` for mocking -- Table-driven tests are the standard pattern - -### Opportunities - -| Feature | Where to Apply | Impact | -|---------|---------------|--------| -| `T.Context()` | Tests using `context.Background()` | Cleaner test lifecycle | -| `T.Chdir()` | Tests with manual `os.Chdir` | Safer directory changes | -| `B.Loop()` | Benchmarks using `for i := 0; i < b.N; i++` | Cleaner benchmarks | -| `testing/synctest` | Timeout/retry/polling tests | Deterministic timing | -| `T.ArtifactDir()` | Tests generating output files | Organized test artifacts | -| `testing/cryptotest.SetGlobalRandom` | Crypto tests needing determinism | Reproducible crypto tests | - ---- - -## Part 4: Security Improvements - -| Feature | Description | azd Relevance | -|---------|-------------|---------------| -| `os.Root` (1.24/1.25) | Path-traversal-safe file access | Project file reading, extension sandbox | -| `net/http.CrossOriginProtection` (1.25) | Built-in CSRF via Fetch Metadata | Local dev server callbacks | -| Post-quantum TLS (1.26) | `SecP256r1MLKEM768`, `SecP384r1MLKEM1024` | Automatic via Go runtime | -| RSA min 1024-bit (1.24) | Enforced by crypto/rsa | Automatic | -| Heap base randomization (1.26) | ASLR improvement | Automatic | - ---- - -## Part 5: PR-Sized Adoption Roadmap - -Each item below is scoped to a single, small PR that can be reviewed independently. - ---- - -### PR 1: `go fix` automated modernizations -**Files:** All `.go` files (auto-applied) -**Changes:** `interface{}` → `any`, loop simplifications, other `go fix` modernizers -**Review notes:** Pure mechanical transform — reviewer just confirms no behavioral changes -**Commands:** -```bash -go fix ./... -gofmt -s -w . -``` - ---- - -### PR 2: Built-in `min()` and `slices.Sort` quick wins -**Files:** 3 files, ~5 lines changed -**Changes:** -- `pkg/account/subscriptions_manager.go`: `math.Min(float64, float64)` → `min()` -- `internal/agent/tools/io/file_search.go`: `sort.Strings` → `slices.Sort` -- `extensions/azure.ai.finetune/internal/cmd/validation.go`: `sort.Strings` → `slices.Sort` - ---- - -### PR 3: `slices.Clone` and `slices.Sorted(maps.Keys(...))` one-liners -**Files:** 2 files, ~6 lines changed -**Changes:** -- `pkg/project/service_target_external.go`: `append([]T{}, s...)` → `slices.Clone(s)` -- `pkg/azdext/scope_detector.go`: manual keys collect + sort → `slices.Sorted(maps.Keys(...))` - ---- - -### PR 4: `sort.Slice` → `slices.SortFunc` in account package -**Files:** 2 files (~4 call sites) -**Changes:** -- `pkg/account/subscriptions_manager.go`: `sort.Slice` → `slices.SortFunc` with `cmp.Compare` -- `pkg/account/subscriptions.go`: 3 × `sort.Slice` → `slices.SortFunc` - ---- - -### PR 5: `sort.Slice` → `slices.SortFunc` in remaining packages -**Files:** ~4 files -**Changes:** -- `pkg/infra/provisioning_progress_display.go` -- Other non-test files with `sort.Slice` -- Does **not** touch `sort.Sort` on `sort.Interface` types (leave those as-is) - ---- - -### PR 6: `http.NewRequest` → `http.NewRequestWithContext` -**Files:** 6 files -**Changes:** -- `tools/avmres/main.go` -- `internal/telemetry/appinsights-exporter/transmitter.go` -- `extensions/azure.ai.agents/internal/project/parser.go` (3 sites) -- `pkg/llm/github_copilot.go` -- Thread existing `ctx` or use `context.Background()` explicitly - ---- - -### PR 7: Range-over-int modernization -**Files:** 3 files, ~3 lines each -**Changes:** -- `pkg/password/generator_test.go`: `for i := 0; i < len(choices); i++` → `for i := range len(choices)` -- `cmd/extensions.go`: similar -- `pkg/output/table.go`: `for i := 0; i < v.Len(); i++` → `for i := range v.Len()` -- Skips parser loops where `i` is modified inside the body - ---- - -### PR 8: Generic `SyncMap[K, V]` utility type -**Files:** 1 new file + 8 files updated -**Changes:** -- Create `pkg/sync/syncmap.go` with `SyncMap[K, V]` generic wrapper -- Update 8 files to use typed map instead of `sync.Map` + type assertions - ---- - -### PR 9: Adopt `errors.AsType[T]` — Azure API error paths -**Files:** `pkg/azapi/` (focused scope) -**Changes:** -- Replace `var errT *T; errors.As(err, &errT)` → `errT, ok := errors.AsType[*T](err)` -- Start with the highest-traffic error handling paths - ---- - -### PR 10: Adopt `errors.AsType[T]` — infra/provisioning -**Files:** `pkg/infra/provisioning/` and `pkg/infra/` -**Changes:** Same pattern as PR 9, scoped to infrastructure error handling - ---- - -### PR 11: Adopt `errors.AsType[T]` — commands and actions -**Files:** `cmd/` and `internal/cmd/` -**Changes:** Same pattern, scoped to command-level error handling - ---- - -### PR 12: Adopt `WaitGroup.Go` in concurrent patterns -**Files:** 4-6 files with goroutine fan-out -**Changes:** -- `pkg/account/subscriptions_manager.go`: parallel tenant fetching -- `pkg/project/`: parallel service operations -- Other files with `wg.Add(1); go func() { defer wg.Done(); ... }()` pattern -- Replace with `wg.Go(func() { ... })` - ---- - -### PR 13: Test modernization — `T.Context()` adoption (batch 1) -**Files:** ~20 test files (scoped to one package, e.g., `pkg/project/`) -**Changes:** -- Replace `context.Background()` / `context.TODO()` → `t.Context()` in tests -- One package at a time to keep PRs reviewable - ---- - -### PR 14: Test modernization — `T.Chdir()` adoption -**Files:** Test files with manual `os.Chdir` + defer restore -**Changes:** Replace with `t.Chdir(dir)` — auto-restored when test ends - ---- - -### Future PRs (require design discussion first) - -| PR | Topic | Notes | -|----|-------|-------| -| 15+ | `omitzero` JSON tags | Audit all JSON structs with `time.Time` fields | -| 16+ | `os.Root` secure file access | Needs design doc for project file reading | -| 17+ | `slog` structured logging | 309 sites — needs architecture decision | -| 18+ | `testing/synctest` for retry/timeout tests | Evaluate which tests benefit | -| 19+ | `tool` directives in `go.mod` | Replace `tools.go` pattern | - ---- - -## Appendix A: Already Modern ✅ - -The codebase already excels in these areas: -- **266 usages** of `slices.*` / `maps.*` packages -- `slices.Sorted(maps.Keys(...))` pattern in 6+ places -- `maps.Clone`, `maps.Copy` used correctly -- `context.WithoutCancel` used where appropriate -- `errors.Join` for multi-error aggregation -- Zero `ioutil.*` — fully migrated -- `any` used throughout (only 7 hand-written `interface{}` remain) - -## Appendix B: No Action Needed - -| Area | Status | -|------|--------| -| `ioutil` migration | ✅ Complete | -| `context.WithoutCancel` | ✅ Already used | -| `errors.Join` | ✅ Already used (12 sites) | -| `slices.Contains` | ✅ Used extensively | -| Crypto/TLS config | ✅ Modern (TLS 1.2 minimum) | -| File I/O patterns | ✅ Uses `os.ReadFile`/`os.WriteFile` | diff --git a/tmp/reports/azd-telemetry-28d-report.md b/tmp/reports/azd-telemetry-28d-report.md deleted file mode 100644 index 1eceb672e7c..00000000000 --- a/tmp/reports/azd-telemetry-28d-report.md +++ /dev/null @@ -1,342 +0,0 @@ -# Azure Developer CLI (azd) — Rolling 28-Day Telemetry Report - -**Period:** Feb 21 – Mar 18, 2026 -**Source:** `ddazureclients.kusto.windows.net` / `DevCli` database -**Generated:** 2026-03-21 - ---- - -## Executive Summary - -| Metric | Value | -|--------|-------| -| **Total Executions** | 10,178,008 | -| **Unique Users** (DevDeviceId) | 129,003 | -| **Overall Success Rate** | 64.5% | -| **Unique Templates Used** | 2,533 | -| **Unique Azure Subscriptions** | 9,183 | -| **Unique Customer Orgs** | 3,857+ | - -### Monthly KPI Comparison (from AzdKPIs table) - -| KPI | Jan 2026 | Feb 2026 | Δ | -|-----|----------|----------|---| -| MAU (Active1d) | 16,636 | 18,811 | **+13.1%** | -| Engaged (2d) | 3,950 | 4,072 | +3.1% | -| Dedicated (10d) | 369 | 364 | -1.4% | -| Provisions | 59,152 | 63,881 | +8.0% | -| Deployments | 54,958 | 56,728 | +3.2% | -| Successful Provisions | 28,835 | 30,742 | +6.6% | -| Successful Deployments | 41,058 | 43,186 | +5.2% | -| Azure Subscriptions | 6,420 | 7,339 | **+14.3%** | - ---- - -## Daily Active Users Trend - -| Date | DAU | Executions | -|------|-----|-----------| -| Feb 21 (Fri) | 504 | 26,306 | -| Feb 22 (Sat) | 3,131 | 142,943 | -| Feb 23 (Sun) | 9,505 | 434,029 | -| Feb 24 (Mon) | 11,499 | 575,189 | -| Feb 25 (Tue) | 11,570 | 622,173 | -| Feb 26 (Wed) | 10,742 | 481,576 | -| Feb 27 (Thu) | 9,665 | 397,341 | -| Feb 28 (Fri) | 3,566 | 223,880 | -| Mar 1 (Sat) | 3,244 | 254,988 | -| Mar 2 (Sun) | 10,093 | 491,753 | -| Mar 3 (Mon) | 10,522 | 458,583 | -| Mar 4 (Tue) | 10,458 | 580,816 | -| Mar 5 (Wed) | 10,359 | 518,339 | -| Mar 6 (Thu) | 9,489 | 500,349 | -| Mar 7 (Fri) | 3,506 | 247,362 | -| Mar 8 (Sat) | 3,267 | 168,655 | -| Mar 9 (Sun) | 10,356 | 563,312 | -| Mar 10 (Mon) | 11,275 | 519,414 | -| Mar 11 (Tue) | 11,004 | 382,957 | -| Mar 12 (Wed) | 10,802 | 425,650 | -| Mar 13 (Thu) | 10,106 | 311,655 | -| Mar 14 (Fri) | 3,586 | 348,198 | -| Mar 15 (Sat) | 3,397 | 162,191 | -| Mar 16 (Sun) | 10,616 | 333,201 | -| Mar 17 (Mon) | 11,241 | 558,356 | -| Mar 18 (Tue) | 11,699 | 448,755 | - -> Weekday DAU averages ~10,500–11,500 users. Weekend dips to ~3,200–3,600. Pattern is healthy and consistent. - ---- - -## Top Commands (by executions) - -| Command | Executions | Unique Users | Success % | -|---------|-----------|-------------|-----------| -| `azd auth token` | 8,237,762 | 20,692 | 58.9% | -| `azd env set` | 428,612 | 33,758 | 99.5% | -| `azd auth login` | 208,951 | 72,188 | 88.3% | -| `azd env get-values` | 190,212 | 24,792 | 98.9% | -| `azd env get-value` | 173,396 | 10,748 | 84.3% | -| `azd env list` | 153,117 | 18,883 | 99.1% | -| **`azd provision`** | **127,640** | **40,469** | **58.8%** | -| **`azd deploy`** | **115,974** | **43,735** | **80.8%** | -| `azd package` | 76,273 | 20,476 | 92.8% | -| **`azd up`** | **71,276** | **11,110** | **31.0%** ⚠️ | -| `azd config set` | 62,626 | 40,166 | 100% | -| `azd env new` | 47,364 | 29,470 | 93.5% | -| `azd init` | 46,133 | 6,251 | 91.0% | -| `azd env select` | 39,631 | 26,229 | 84.2% | - ---- - -## Top Templates (by unique users) - -| Template | Users | Executions | -|----------|-------|-----------| -| **azd-init** (interactive init) | 8,427 | 81,078 | -| **azure-search-openai-demo** | 2,542 | 259,785 | -| **chat-with-your-data-solution-accelerator** | 625 | 5,707 | -| **multi-agent-custom-automation-engine** | 550 | 19,742 | -| **todo-nodejs-mongo** | 469 | 1,925 | -| **agentic-applications-for-unified-data-foundation** | 461 | 12,653 | - ---- - -## Execution Environments — IDEs & AI Agents - -### All Execution Environments - -| Environment | Executions | Unique Users | User Share | -|-------------|-----------|-------------|------------| -| **Desktop** (terminal) | 8,699,096 | 39,274 | 30.4% | -| **GitHub Actions** | 421,968 | 37,759 | 29.2% | -| **Azure Pipelines** | 358,116 | 32,543 | 25.2% | -| **VS Code** (extension) | 93,024 | 16,006 | 12.4% | -| **Visual Studio** | 76,789 | 4,886 | 3.8% | -| **Azure CloudShell** | 19,456 | 3,686 | 2.9% | -| **Claude Code** 🔥 | 367,193 | 699 | 0.5% | -| **GitHub Codespaces** | 36,097 | 1,066 | 0.8% | -| **GitLab CI** | 36,519 | 1,142 | 0.9% | -| **GitHub Copilot CLI** | 50,115 | 579 | 0.4% | -| **OpenCode** | 3,204 | 38 | — | -| **Gemini** | 102 | 12 | — | - -### 🤖 AI/LLM Agent Deep Dive - -#### Weekly Trend - -| Week | Claude Code Users | Claude Code Exec | Copilot CLI Users | Copilot CLI Exec | OpenCode Users | Gemini Users | -|------|------------------|-----------------|-------------------|-----------------|---------------|-------------| -| Feb 16 | 20 | 1,213 | 9 | 177 | 3 | — | -| Feb 23 | 202 | 28,918 | 185 | 12,362 | 17 | 6 | -| Mar 2 | 263 | 133,593 | 228 | 18,716 | 11 | 2 | -| Mar 9 | **263** | **159,470** | 218 | 11,015 | 14 | 2 | -| Mar 16 | 218 | 43,999 | 173 | 7,845 | 12 | 2 | - -#### Success Rates Over Time - -| Week | Claude Code | Copilot CLI | OpenCode | Gemini | -|------|------------|------------|----------|--------| -| Feb 16 | 23.1% | 79.1% | 75.0% | — | -| Feb 23 | 31.2% | 74.4% | 71.2% | 62.0% | -| Mar 2 | 46.4% | 48.5% | 24.5% | 69.2% | -| Mar 9 | **87.9%** | 69.5% | 66.0% | 80.0% | -| Mar 16 | 82.5% | 63.1% | 74.8% | 20.0% | - -#### Top Commands by AI Agent - -**Claude Code** (367K executions, 699 users): - -| Command | Executions | Users | -|---------|-----------|-------| -| `auth token` | 284,257 | 375 | -| `env list` | 63,530 | 171 | -| `env get-values` | 5,804 | 217 | -| `env set` | 3,859 | 186 | -| `deploy` | 2,920 | 192 | -| `provision` | 1,198 | 170 | -| `up` | 709 | 125 | - -**GitHub Copilot CLI** (50K executions, 579 users): - -| Command | Executions | Users | -|---------|-----------|-------| -| `auth token` | 18,303 | 319 | -| `env get-value` | 6,747 | 69 | -| `env set` | 4,140 | 203 | -| `env get-values` | 3,766 | 214 | -| `deploy` | 3,043 | 187 | -| `provision` | 2,957 | 208 | -| `up` | 1,903 | 172 | - -> Notable: Copilot CLI is also running **MCP operations** (`mcp.validate_azure_yaml`, `mcp.iac_generation_rules`, `mcp.infrastructure_generation`, etc.) — 70 total MCP calls from 19+ users. - -#### AI Agent Key Insights - -- 🔥 **Claude Code is exploding** — went from 20 users to 263 in 4 weeks, with 367K executions (more than Copilot CLI despite fewer users). Heavy `auth token` loop suggests agentic workflow patterns. -- **Claude Code success rate improved dramatically** — from 23% to 88% in 4 weeks, suggesting rapid integration maturation. -- **Copilot CLI** is steady at ~200 users/week with 50K executions. Already using azd's MCP tools. -- **AI agents collectively**: ~1,300 users, 420K executions — about **4% of total azd volume** and growing fast. - ---- - -## Customer Breakdown - -### By Segment - -| Segment | Customers | Users | Subscriptions | Executions | -|---------|-----------|-------|---------------|------------| -| **SMB Commercial** | 1,852 | 26,637 | 2,732 | 473K | -| **Unspecified** | 993 | 21,622 | 2,466 | 445K | -| **Upper Majors Commercial** | 336 | 11,325 | 588 | 83K | -| **Strategic Commercial** | 188 | 5,054 | 564 | 113K | -| **Corporate Commercial** | 234 | 3,370 | 822 | 41K | -| **Upper Majors Public Sector** | 118 | 2,434 | 175 | 19K | -| Majors Growth Commercial | 34 | 418 | 44 | 5K | -| Strategic Public Sector | 29 | 264 | 49 | 5K | - -### Top Customers (by unique users) - -| Customer | Segment | Country | Users | Subs | Executions | Success% | -|----------|---------|---------|-------|------|------------|----------| -| **CoreAI - Platform & Tools** | Internal | 🇺🇸 US | 9,246 | 35 | 73K | 86.9% | -| **Investec Bank** | Upper Majors | 🇬🇧 UK | 6,471 | 74 | 15K | 90.3% | -| **Microsoft** | Internal | 🇺🇸 US | 3,976 | 978 | 149K | 82.8% | -| **Cloud + AI** | Internal | 🇨🇳 CN | 3,066 | 183 | 107K | 96.3% | -| **Vee Friends** | SMB | 🇺🇸 US | 2,585 | 1 | 8K | 99.0% | -| **Grupo Cosan** | Upper Majors | 🇧🇷 BR | 1,667 | 1 | 5K | 100% | -| **Puerto Rico PRITS** | Public Sector | 🇵🇷 PR | 1,422 | 2 | 4K | 94.9% | -| **H&M** | Strategic | 🇸🇪 SE | 1,107 | 40 | 8K | 91.7% | -| **Jersey Telecoms** | Corporate | 🇬🇧 UK | 951 | 2 | 7K | 99.6% | -| **Volkswagen** | Strategic | 🇩🇪 DE | 664 | 17 | 39K | 98.6% | -| **Microsoft Security** | Internal | 🇺🇸 US | 640 | 15 | 14K | 95.7% | -| **IBM** | Strategic | 🇺🇸 US | 385 | 7 | 4K | 83.2% | -| **Deloitte** | Strategic | 🇺🇸 US | 329 | 10 | 1K | 96.3% | -| **bp** | Strategic | 🇬🇧 UK | 271 | 9 | 6K | 97.9% | -| **Allianz Technology** | Strategic | 🇩🇪 DE | 233 | 8 | 11K | 99.8% | -| **Rabobank** | Strategic | 🇳🇱 NL | 248 | 8 | 462 | 92.2% | -| **EY Global** | Strategic | 🇺🇸 US | 201 | 25 | 2K | 71.6% | -| **Mercedes-Benz** | Strategic | 🇩🇪 DE | 195 | 6 | 3K | 99.0% | - ---- - -## Error Analysis - -### Overall Failure Rate Trend - -| Week | Total Exec | Failures | Fail Rate | -|------|-----------|----------|-----------| -| Feb 16 | 168K | 87K | 52.0% | -| Feb 23 | 2,989K | 1,050K | 35.1% | -| Mar 2 | 2,966K | 1,015K | 34.2% | -| Mar 9 | 2,713K | 948K | 35.0% | -| Mar 16 | 1,340K | 513K | 38.3% | - -### Success Rates by Command - -| Command | Total | Failures | Success% | Users | -|---------|-------|----------|----------|-------| -| `auth token` | 8,236,682 | 3,384,478 | **58.9%** | 20,689 | -| `auth login` | 208,930 | 24,476 | 88.3% | 72,184 | -| `provision` | 127,630 | 52,639 | **58.8%** | 40,466 | -| `deploy` | 115,961 | 22,243 | 80.8% | 43,735 | -| `package` | 76,268 | 5,520 | 92.8% | 20,476 | -| **`up`** | **71,271** | **49,154** | **31.0%** ⚠️ | 11,110 | -| `env new` | 47,355 | 3,098 | 93.5% | 29,463 | -| `init` | 46,132 | 4,171 | 91.0% | 6,251 | -| `down` | 15,830 | 2,761 | 82.6% | 2,982 | -| `restore` | 4,979 | 1,993 | **60.0%** | 693 | - -### Top Result Codes (failure reasons) - -| Result Code | Count | Users | Category | -|-------------|-------|-------|----------| -| `UnknownError` | 2,383,049 | 8,128 | Uncategorized | -| `auth.login_required` | 547,007 | 1,984 | Auth | -| `internal.errors_errorString` | 462,344 | 16,978 | Internal | -| `auth.not_logged_in` | 66,331 | 1,769 | Auth | -| `error.suggestion` | 33,762 | 7,475 | User guidance | -| `service.arm.deployment.failed` | 22,033 | 4,025 | ARM | -| `service.aad.failed` | 13,619 | 1,276 | AAD | -| `internal.azidentity_AuthenticationFailedError` | 8,732 | 196 | Auth (identity) | -| `service.arm.validate.failed` | 6,426 | 1,220 | ARM | -| `service.arm.400` | 6,371 | 1,479 | ARM | -| `tool.bicep.failed` | 5,099 | 1,437 | Bicep | -| `tool.pwsh.failed` | 4,931 | 688 | PowerShell | -| `tool.docker.failed` | 4,655 | 1,024 | Docker | -| `tool.dotnet.failed` | 3,732 | 1,345 | .NET | -| `user.canceled` | 3,630 | 1,739 | User action | -| `tool.Docker.missing` | 2,936 | 784 | Docker not installed | -| `tool.terraform.failed` | 2,872 | 355 | Terraform | - -### Provision / Deploy / Up Error Breakdown - -| Command | Error Category | Result Code | Count | Users | -|---------|---------------|-------------|-------|-------| -| `provision` | ARM | arm.deployment.failed | 12,395 | 3,908 | -| `up` | ARM | error.suggestion | 7,036 | 1,928 | -| `up` | ARM | arm.deployment.failed | 4,725 | 1,466 | -| `provision` | ARM | arm.validate.failed | 3,393 | 1,208 | -| `provision` | ARM | arm.400 | 3,130 | 1,143 | -| `provision` | Bicep | bicep.failed | 2,715 | 1,311 | -| `up` | ARM | arm.400 | 2,540 | 940 | -| `provision` | PowerShell | pwsh.failed | 2,053 | 560 | -| `provision` | Terraform | terraform.failed | 1,873 | 335 | -| `deploy` | Docker | docker.failed | 1,652 | 692 | -| `up` | Docker | Docker.missing | 1,028 | 540 | -| `deploy` | .NET | dotnet.failed | 1,304 | 559 | - -### 🔍 `cmd.auth.token` Error Deep Dive - -`auth token` accounts for **3.38M of 3.61M total failures (94%)**. Breakdown: - -| Result Code | Error Category | Count | Users | % of Auth Token Failures | -|-------------|---------------|-------|-------|--------------------------| -| `UnknownError` | unknown | 2,352,993 | 6,403 | **69.5%** | -| `auth.login_required` | unknown | 543,972 | 1,353 | **16.1%** | -| `internal.errors_errorString` | unknown | 398,164 | 5,285 | **11.8%** | -| `auth.not_logged_in` | unknown | 65,643 | 1,233 | 1.9% | -| `service.aad.failed` | aad | 12,246 | 658 | 0.4% | -| `internal.azidentity_AuthenticationFailedError` | unknown | 7,258 | 65 | 0.2% | - -> ⚠️ **Classification gap:** 99.6% of auth token failures are categorized as "unknown" even when result codes like `auth.login_required` and `auth.not_logged_in` provide clear signals. Only `service.aad.failed` (12K) gets properly categorized. - -#### Auth Token Failure Rate by Execution Environment - -| Environment | Total | Failures | Fail % | -|-------------|-------|----------|--------| -| **Desktop** | 7,888,323 | 3,250,684 | **41.2%** | -| **Claude Code** | 284,257 | 116,750 | **41.1%** | -| **GitHub Copilot CLI** | 18,302 | 11,156 | **61.0%** ⚠️ | -| **GitHub Actions** | 12,915 | 383 | 3.0% ✅ | -| **GitHub Codespaces** | 10,580 | 1,417 | 13.4% | -| **Azure Pipelines** | 9,692 | 2,219 | 22.9% | -| **GitLab CI** | 5,147 | 0 | 0% ✅ | -| **OpenCode** | 2,053 | 908 | 44.2% | - -> CI/CD environments have near-zero auth token failures (service principal auth). Interactive/agent environments hit ~41-61% failure rates due to token expiry and "not logged in" retries. - ---- - -## Key Takeaways & Recommendations - -### 📈 Growth Signals -1. **MAU up 13% MoM**, Azure subscriptions up **14%** — strong growth trajectory -2. **AI agent adoption is hockey-sticking** — Claude Code went from 20→263 users in 4 weeks -3. **3,857+ customer organizations** actively using azd, with marquee enterprise logos (VW, H&M, bp, Mercedes, Allianz, IBM) - -### ⚠️ Areas of Concern -1. **`azd up` at 31% success rate** — the flagship command. Compounds provision + deploy failures. 49K failures affecting 11K users. -2. **`auth token` dominates failure volume** — 3.38M failures, but most are expected retry patterns (token refresh). Inflates overall failure rate from ~6% (excluding auth token) to ~35%. -3. **Error classification is broken** — 2.35M errors classified as `UnknownError` with no detail. `auth.login_required` (544K) is categorized as "unknown" despite having a clear result code. - -### 🔧 Suggested Actions -1. **Fix error categorization** — Map `auth.login_required`, `auth.not_logged_in`, and `azidentity_AuthenticationFailedError` result codes to an `auth` error category instead of `unknown`. -2. **Investigate `UnknownError`** (2.35M) — Add better error introspection to surface what's actually failing. -3. **Improve `azd up` reliability** — At 31% success, the compound command needs better pre-flight checks, clearer error messages, and possibly staged rollback. -4. **Address Docker-not-installed** (2,936 failures, 784 users) — Better pre-req detection and user guidance before attempting container deployments. -5. **Optimize for AI agents** — Claude Code and Copilot CLI are growing fast. Consider agent-specific auth flows (non-interactive token acquisition) and reducing the `auth token` retry loop noise. -6. **Monitor EY Global** — 71.6% success rate for a Strategic account is below the 90%+ benchmark seen at other enterprise customers. - ---- - -*Report generated from `AzdOperations`, `AzdKPIs`, and `Templates` tables on the `ddazureclients` Kusto cluster.* diff --git a/tmp/reports/azd-telemetry-opportunities-report.md b/tmp/reports/azd-telemetry-opportunities-report.md deleted file mode 100644 index 6ca6f121e49..00000000000 --- a/tmp/reports/azd-telemetry-opportunities-report.md +++ /dev/null @@ -1,231 +0,0 @@ -# azd Telemetry: Improvement Opportunities Report - -**Period:** Rolling 28 days (Feb 21 – Mar 18, 2026) -**Generated:** 2026-03-22 (updated 2026-03-23) -**Source:** Telemetry report + GitHub issue/PR cross-reference - ---- - -## Executive Summary - -azd processes **10.2M executions/28d** with a **64.5% overall success rate**. However, this headline number is misleading — `auth token` alone accounts for **3.38M of 3.61M failures (94%)**. Excluding auth token, the real command failure rate is closer to **~6%**. - -The three largest improvement opportunities are: - -| Opportunity | Volume Impact | Status | -|---|---|---| -| 1. **UnknownError bucket** | 2.35M errors with zero classification | 🟢 PR #7241 (in review) | -| 2. **Auth error misclassification** | 610K errors in wrong category | 🟢 PR #7235 (all CI green) | -| 3. **`azd up` at 31% success** | 49K failures, 11K users | 🟢 PR #7236 (auth pre-flight, all CI green) | - ---- - -## Opportunity 1: UnknownError — The Biggest Blind Spot - -### Scale -- **2,352,993 errors/28d** classified as `UnknownError` -- **6,403 users** affected -- **69.5%** of all `auth token` failures -- Currently provides **zero diagnostic signal** - -### Root Cause (confirmed) -`EndWithStatus(err)` in several high-volume code paths bypasses `MapError()` — the central error classification function. The telemetry converter then falls back to `"UnknownFailure"` when the span status description is empty or unrecognized. - -### Fix: PR #7241 (in review) -- MCP tool handler and Copilot agent spans now route through `MapError()` -- `EndWithStatus` fallback prefixed with `internal.` for consistency - -### Existing Work -| Issue/PR | Status | Coverage | -|---|---|---| -| **#7239** / PR #7241 | 🟢 PR open, CI green | Routes spans through MapError | -| #6796 "Reduce Unknown error bucket" | ✅ Closed (partial fix) | Addressed some categories | -| #6576 "Follow up on unknown errors classification" | ✅ Closed | Follow-up from #6796 | -| #5743 "Better unknown error breakdowns" | ✅ Closed | Added `errorType()` introspection | - ---- - -## Opportunity 2: Auth Error Classification ✅ IN PROGRESS - -### Scale -- **610K errors/28d** miscategorized as `"unknown"` instead of `"aad"` -- **~17%** of all auth token errors - -### Fix: PR #7235 (all CI green, awaiting review) -| Error | Before | After | -|---|---|---| -| `auth.login_required` (544K) | unknown ❌ | aad ✅ | -| `auth.not_logged_in` (66K) | unknown ❌ | aad ✅ | -| `azidentity.AuthenticationFailedError` (8.7K) | unknown ❌ | aad ✅ (new code: `auth.identity_failed`) | - -### Related Issues -| Issue | Status | Relationship | -|---|---|---| -| **#7233** | 🟢 PR #7235 open | Direct fix | -| #7104 "Revisit UserContextError categories" | 🟡 Open | Downstream — better error buckets for agents | - ---- - -## Opportunity 3: `azd up` Reliability (31% Success Rate) - -### Scale -- **49,154 failures/28d**, **11,110 users** -- The flagship compound command (provision + deploy) -- Failure cascades: a provision failure = `up` failure even if deploy would succeed - -### Error Breakdown for `azd up` -| Error Category | Count | Users | Preventable? | -|---|---|---|---| -| `error.suggestion` | 7,036 | 1,928 | ⚠️ Partially — many are auth errors wrapped in suggestions | -| `arm.deployment.failed` | 4,725 | 1,466 | ❌ Infrastructure errors (quota, policy, region) | -| `arm.400` | 2,540 | 940 | ⚠️ Some are preventable with validation | -| `Docker.missing` | 1,028 | 540 | ✅ **100% preventable** with pre-flight check | - -### Existing Work -| Issue/PR | Status | Coverage | -|---|---|---| -| **#7234** / PR #7236 | 🟢 PR open, CI green | Auth pre-flight + `--check` flag for agents | -| **#7240** Docker pre-flight | 🟡 Open (assigned spboyer) | Suggest remoteBuild when Docker missing | -| **#7179** Preflight: Azure Policy blocking local auth | 🟢 PR open (vhvb1989) | Detects policy-blocked deployments early | -| **#7115** Never abort deployment on validation errors | 🟡 Open | More resilient deploy behavior | -| **#3533** Verify dependency tools per service | 🟡 Open (good first issue) | Check only tools needed, not all | - -### Recommended Next Steps -1. **Ship #7234** (auth pre-flight middleware) — prevents late auth failures -2. **Ship #7240** (Docker pre-flight) — catch Docker/tool missing before starting -3. **Decompose `up` failure reporting** — show provision vs deploy failures separately so users know what succeeded -4. **Add ARM quota pre-flight** — check quota before deploying (prevents the most common ARM failures) - ---- - -## Opportunity 4: `internal.errors_errorString` — Untyped Errors - -### Scale -- **462,344 errors/28d**, **16,978 users** -- These are bare `errors.New()` calls without typed sentinels -- Each one is a classification opportunity lost - -### What's Happening -Code paths that return `errors.New("something failed")` or `fmt.Errorf("failed: %w", err)` where the inner error is also untyped end up in the catch-all bucket with the opaque name `errors_errorString`. - -### Recommended Next Steps -1. **Audit hot paths** — Find the top `errors.New()` call sites in auth token, provision, and deploy code paths -2. **Add typed sentinels progressively** — Start with the highest-volume error messages -3. **Test enforcement** — The test suite already has `allowedCatchAll` enforcement (errors_test.go line 791). Expand this pattern to prevent regressions. - ---- - -## Opportunity 5: AI Agent Optimization - -### Scale -- **420K executions/28d** from AI agents (~4% of total, growing fast) -- Claude Code: **367K executions**, 699 users (exploding growth) -- Copilot CLI: **50K executions**, 579 users (using MCP tools) - -### Key Pain Points -| Issue | Impact | Status | -|---|---|---| -| `auth token` failure rate: 61% (Copilot CLI), 41% (Claude Code) | Agents retry in loops, wasting cycles | 🟢 PR #7236 (`--check` flag) | -| No machine-readable auth status | Agents parse error messages | 🟢 PR #7236 (`expiresOn` in JSON) | -| Agent init errors lack remediation hints | Adoption barrier | 🟡 #6820 open | -| Extension compatibility issues | `env refresh` broken with agents ext | 🟡 #7195 open | - -### Existing Work -| Issue/PR | Status | Coverage | -|---|---|---| -| **#7234** / PR #7236 | 🟢 PR open, CI green | `azd auth token --check` + `expiresOn` in status | -| **#7202** Evaluation and testing framework | 🟢 PR open (spboyer) | Systematic testing for agent flows | -| **#6820** Agent init error remediation hints | 🟡 Open | Better error UX for agent setup | -| **#7195** `env refresh` + agent extension | 🟡 Open | Compatibility fix | -| **#7156** Hosted agent toolsets | 🟡 Open | Azure AI Foundry integration | - ---- - -## Opportunity 6: ARM Deployment Resilience - -### Scale -- **22,033 `arm.deployment.failed`/28d**, 4,025 users -- **6,426 `arm.validate.failed`/28d**, 1,220 users -- **6,371 `arm.400`/28d**, 1,479 users - -### Existing Work -| Issue/PR | Status | Coverage | -|---|---|---| -| **#6793** Investigate 500/503 ARM errors | 🟡 Open | ~707 preventable transient failures | -| **#7179** Preflight: Azure Policy detection | 🟢 PR open | Catches policy blocks before deploy | - -### Recommended Next Steps -1. **Add retry logic for transient 500/503** (#6793) — ~707 preventable failures -2. **Pre-flight quota check** — many ARM 400s are quota exceeded -3. **Surface inner deployment error codes** in user-facing messages - ---- - -## Opportunity 7: Tool Installation UX - -### Scale -- **Docker.missing**: 2,936 failures, 784 users -- **tool.docker.failed**: 4,655 failures, 1,024 users -- **tool.dotnet.failed**: 3,732 failures, 1,345 users -- **tool.bicep.failed**: 5,099 failures, 1,437 users - -### Existing Work -| Issue/PR | Status | Coverage | -|---|---|---| -| **#7240** Docker pre-flight + remoteBuild suggestion | 🟡 Open (assigned spboyer) | Detect upfront, suggest alternative | -| **#5715** Docker missing → suggest remoteBuild | 🟡 Open | Original request (superseded by #7240) | -| **#3533** Per-service tool validation | 🟡 Open (good first issue) | Only check needed tools | -| **#6424** Pre-tool-validation lifecycle event | 🟡 Open | Extension hook for guidance | - ---- - -## Deeper Questions To Investigate - -These are questions the current data could answer but the report doesn't address: - -### Metric Integrity -1. **Report two success rates** — Overall (64.5%) AND excluding auth token (~94%). The current headline is misleading; `auth token` is 80% of all executions and dominates every metric. - -### `azd up` Decomposition -2. **Stage-level failure attribution** — Where in the `up` flow does it fail? 70% at provision, 20% at deploy, 10% at both? Without this, we can't prioritize which stage to fix first. - -### Hidden Problem Commands -3. **`azd restore` at 60% success** — 1,993 failures, 693 users. A restore operation failing 40% of the time is a red flag nobody's investigating. -4. **`env get-value` at 84.3%** — A read-only key lookup failing 15.7% for 10,748 users. Likely missing env/key/project — but should be surfaced as a UX problem, not swallowed. -5. **`auth login` at 88.3%** — 24,476 login failures. Login should approach 100%. What's actually failing? - -### Template & Customer Analysis -6. **Template × command success matrix** — `azure-search-openai-demo` has 259K executions. If that template has a high failure rate, it skews numbers for 2,542 users. Need per-template success rates. -7. **Strategic account success variance** — EY at 71.6% vs Allianz at 99.8%. Same tool, 28-point gap. Understanding WHY (template? region? subscription policies?) enables targeted customer success. - -### Error Quality -8. **What's inside `error.suggestion`?** — 33,762 errors classified as `error.suggestion` but the inner error varies wildly. The `classifySuggestionType` inner code exists in telemetry — should be surfaced alongside the wrapper code. - -### User Journey & Retention -9. **End-to-end funnel** — `init` (91%) → `provision` (58.8%) → `deploy` (80.8%). What's the **cohort completion rate**? If a user inits, what % ever successfully deploy? -10. **First-run vs repeat failure rate** — Are tool failures (Docker, Bicep, dotnet) concentrated on first-run users or recurring? First-run = onboarding UX fix. Recurring = bug. -11. **Failure → churn correlation** — Engaged (2+ days) is only 4,072/18,811 MAU (21.6%). Are users who hit `azd up` failures coming back? If not, the 31% success rate is also a **retention problem**. - -### AI Agent Journey -12. **Agent → deployment conversion** — Agents call `auth token` heavily but how many get to `provision`/`deploy`? What's the agent user journey funnel vs desktop users? - ---- - -## Priority Matrix (Updated) - -| Priority | Opportunity | Volume | Effort | Status | -|---|---|---|---|---| -| 🟢 **In Review** | UnknownError classification | 2.35M | Medium | PR #7241 | -| 🟢 **In Review** | Auth error categories | 610K | Small | PR #7235 | -| 🟢 **In Review** | Auth pre-flight + agent `--check` | 610K cascade | Medium | PR #7236 | -| 🟡 **Filed** | Docker pre-flight | 2.9K + 4.7K | Small | #7240 | -| 🟡 **P1** | `azd up` decomposition | 49K | Medium | Needs Kusto query | -| 🟡 **P2** | `errors_errorString` audit | 462K | Large (ongoing) | No active issue | -| 🟡 **P2** | Template success matrix | Unknown | Small (query) | Needs Kusto query | -| 🟡 **P2** | `restore` / `env get-value` investigation | 4K | Small | No active issue | -| 🟡 **P2** | ARM resilience | 35K | Medium | #6793, #7179 | -| 🟢 **P3** | AI agent journey analysis | 420K | Small (query) | Needs Kusto query | - ---- - -*Cross-referenced against Azure/azure-dev open issues and PRs as of 2026-03-23.* diff --git a/tmp/reports/docker-missing-investigation.md b/tmp/reports/docker-missing-investigation.md deleted file mode 100644 index d73308a36a1..00000000000 --- a/tmp/reports/docker-missing-investigation.md +++ /dev/null @@ -1,101 +0,0 @@ -# Docker Missing: Investigation & Implementation Plan - -**Generated:** 2026-03-22 -**Issue:** #5715, #3533 -**Telemetry:** 2,936 failures, 784 users / 28 days - ---- - -## Problem - -When Docker is not installed, `azd deploy`/`up` fails with `tool.Docker.missing` but **doesn't tell users they can use `remoteBuild: true`** — a built-in alternative that builds containers on Azure instead of locally. - -This affects Container Apps, AKS, and some Function App deployments where Docker is listed as required but isn't actually needed when remote builds are enabled. - ---- - -## Architecture - -``` -azd deploy - → projectManager.EnsureServiceTargetTools() - → containerHelper.RequiredExternalTools() - → if remoteBuild: true → [] (no Docker needed!) - → if remoteBuild: false/nil → [docker] (Docker required) - → tools.EnsureInstalled(docker) - → docker.CheckInstalled() → MissingToolErrors -``` - -### Key Insight - -`ContainerHelper.RequiredExternalTools()` (`pkg/project/container_helper.go:250-260`) already skips Docker when `remoteBuild: true`. The user just doesn't know this option exists. - ---- - -## What Supports remoteBuild - -| Service Target | remoteBuild Support | Default | Docker Required Without It | -|---|---|---|---| -| **Container Apps** | ✅ Yes | Not set | Yes | -| **AKS** | ✅ Yes | Not set | Yes | -| **Function App** (Flex) | ✅ Yes | JS/TS/Python: true | Varies | -| **App Service** | ❌ No | N/A | Yes (if containerized) | -| **Static Web App** | ❌ No | N/A | No | - ---- - -## Proposed Fix - -### Where to change: `internal/cmd/deploy.go` (after `EnsureServiceTargetTools`) - -When `MissingToolErrors` is caught with Docker in the tool list: - -1. Check which services actually need Docker (considering remoteBuild support) -2. If all services support remoteBuild → suggest it as primary alternative -3. If some do, some don't → suggest it for eligible services + install Docker for the rest - -### Error flow (proposed): - -```go -if toolErr, ok := errors.AsType[*tools.MissingToolErrors](err); ok { - if slices.Contains(toolErr.ToolNames, "Docker") { - // Check if services can use remoteBuild instead - return nil, &internal.ErrorWithSuggestion{ - Err: toolErr, - Suggestion: "Your services can build on Azure instead of locally. " + - "Add 'docker: { remoteBuild: true }' to each service in azure.yaml, " + - "or install Docker: https://aka.ms/azure-dev/docker-install", - } - } -} -``` - -### Files to modify - -| File | Change | -|---|---| -| `cli/azd/internal/cmd/deploy.go` | Catch Docker missing, wrap with remoteBuild suggestion | -| `cli/azd/pkg/project/project_manager.go` | Add helper to detect remoteBuild-capable services | -| `cli/azd/pkg/tools/docker/docker.go` | Optionally enhance base error message | -| `cli/azd/internal/cmd/deploy_test.go` | Test for suggestion when Docker missing | - -### Same pattern needed in: -- `internal/cmd/provision.go` (calls `EnsureAllTools`) -- `internal/cmd/up.go` (compound command) - ---- - -## Expected Impact - -- **2,936 failures/28d** get actionable guidance instead of a dead-end error -- **784 users** learn about remoteBuild as an alternative -- Users without Docker can still deploy to Container Apps and AKS -- Aligns with existing `ErrorWithSuggestion` pattern used throughout the codebase - ---- - -## Related Issues - -- **#5715** — "When docker is missing, suggest to use remoteBuild: true" (open) -- **#3533** — "Operations should verify presence of dependency tools needed for service" (open, good first issue) -- **#7239** — UnknownError investigation (newly created P0) From 86ab425b14538da139474a679a19d7aaeea37c64 Mon Sep 17 00:00:00 2001 From: Shayne Boyer Date: Mon, 23 Mar 2026 18:04:01 -0700 Subject: [PATCH 07/11] Address review: exit non-zero in both modes, fix double output Per @JeffreyCA feedback: - Return auth.ErrNoCurrentUser when unauthenticated in both JSON and interactive modes (exit non-zero in all cases) - In JSON mode, format output before returning error to avoid double-print - In interactive mode, show status UX then exit non-zero Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- cli/azd/cmd/auth_status.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/cli/azd/cmd/auth_status.go b/cli/azd/cmd/auth_status.go index b0962456de4..4a7ef2d95fc 100644 --- a/cli/azd/cmd/auth_status.go +++ b/cli/azd/cmd/auth_status.go @@ -117,10 +117,11 @@ func (a *authStatusAction) Run(ctx context.Context) (*actions.ActionResult, erro if a.formatter.Kind() != output.NoneFormat { a.formatter.Format(res, a.writer, nil) - // In machine-readable mode, exit non-zero when unauthenticated so agents - // can rely on the exit code without parsing output. if res.Status == contracts.AuthStatusUnauthenticated { - return nil, fmt.Errorf("not authenticated") + // Return ErrNoCurrentUser so the process exits non-zero. + // The JSON output is already written above; the UX middleware + // skips formatting for JSON output, so this won't double-print. + return nil, auth.ErrNoCurrentUser } return nil, nil } @@ -156,6 +157,10 @@ func (a *authStatusAction) Run(ctx context.Context) (*actions.ActionResult, erro "The authentication mode is controlled externally and cannot be changed from within azd.") } + if res.Status == contracts.AuthStatusUnauthenticated { + return nil, auth.ErrNoCurrentUser + } + return nil, nil } From 99d50748aacf04696c54c8ef0f7c8db3211a73f3 Mon Sep 17 00:00:00 2001 From: Shayne Boyer Date: Tue, 24 Mar 2026 06:29:35 -0700 Subject: [PATCH 08/11] Revert non-zero exit for unauthenticated status Per @vhvb1989 feedback: unauthenticated is a valid result, not a command failure. Non-zero exit should only be for unexpected errors. The expiresOn and LoginGuardMiddleware improvements remain. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- cli/azd/cmd/auth_status.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/cli/azd/cmd/auth_status.go b/cli/azd/cmd/auth_status.go index 4a7ef2d95fc..b27dacc334a 100644 --- a/cli/azd/cmd/auth_status.go +++ b/cli/azd/cmd/auth_status.go @@ -117,12 +117,6 @@ func (a *authStatusAction) Run(ctx context.Context) (*actions.ActionResult, erro if a.formatter.Kind() != output.NoneFormat { a.formatter.Format(res, a.writer, nil) - if res.Status == contracts.AuthStatusUnauthenticated { - // Return ErrNoCurrentUser so the process exits non-zero. - // The JSON output is already written above; the UX middleware - // skips formatting for JSON output, so this won't double-print. - return nil, auth.ErrNoCurrentUser - } return nil, nil } @@ -157,10 +151,6 @@ func (a *authStatusAction) Run(ctx context.Context) (*actions.ActionResult, erro "The authentication mode is controlled externally and cannot be changed from within azd.") } - if res.Status == contracts.AuthStatusUnauthenticated { - return nil, auth.ErrNoCurrentUser - } - return nil, nil } From 3b9620a85eb662a04f202722dead56cff74950cc Mon Sep 17 00:00:00 2001 From: Shayne Boyer Date: Tue, 24 Mar 2026 10:17:43 -0700 Subject: [PATCH 09/11] feat: azd init -t auto-creates project directory like git clone When using azd init -t