From fe61db8d343f883452e9ba9d17e3e3cb184aacfa Mon Sep 17 00:00:00 2001 From: hemarina Date: Fri, 27 Feb 2026 18:23:51 -0800 Subject: [PATCH 1/5] Simplify error troubleshooting workflow with two-step guidance - Reduce 'Generate troubleshooting steps?' options to: Explain the error, Skip, Always skip - After explain, prompt 'Do you want to generate step by step fix guidance?' with Yes/No - Selecting 'No, I know what to do' exits agent mode immediately - Structure explain output with bold 'What's happening' and 'Why it's happening' sections - Remove guide/summarize/always-explain/always-guide/always-summarize options --- cli/azd/cmd/middleware/error.go | 115 ++++++++++++++++++++------------ 1 file changed, 72 insertions(+), 43 deletions(-) diff --git a/cli/azd/cmd/middleware/error.go b/cli/azd/cmd/middleware/error.go index e4321166a95..bf7dc26f682 100644 --- a/cli/azd/cmd/middleware/error.go +++ b/cli/azd/cmd/middleware/error.go @@ -162,51 +162,64 @@ func (e *ErrorMiddleware) Run(ctx context.Context, next NextFn) (*actions.Action e.console.Message(ctx, "") troubleshootScope, err := e.promptTroubleshootingWithConsent(ctx) + troubleshootScope, err := e.promptTroubleshootingWithConsent(ctx) if err != nil { span.SetStatus(codes.Error, "agent.consent.failed") return nil, fmt.Errorf("prompting for troubleshooting scope: %w", err) + return nil, fmt.Errorf("prompting for troubleshooting scope: %w", err) } if troubleshootScope != "" { - var troubleshootPrompt string - switch troubleshootScope { - case "explain": - troubleshootPrompt = fmt.Sprintf( - `Steps to follow: + troubleshootPrompt := fmt.Sprintf( + `Steps to follow: 1. Use available tools including azd_error_troubleshooting tool to identify this error. - 2. Provide a concise explanation of the error and its root cause in 3-5 sentences. - DO NOT return JSON. Use plain text with minimal markdown formatting. + 2. Provide a concise explanation using these two sections with bold markdown titles: + **What's happening**: Explain what the error is in 1-2 sentences. + **Why it's happening**: Explain the root cause in 1-3 sentences. + DO NOT return JSON. Use plain text with minimal markdown formatting beyond the section titles. Do not provide fix steps. Do not perform any file changes. Error details: %s`, errorInput) - case "guide": - troubleshootPrompt = fmt.Sprintf( - `Steps to follow: + + agentOutput, err := azdAgent.SendMessage(ctx, troubleshootPrompt) + + if err != nil { + e.displayAgentResponse(ctx, agentOutput, AIDisclaimer) + span.SetStatus(codes.Error, "agent.send_message.failed") + return nil, err + } + + e.displayAgentResponse(ctx, agentOutput, AIDisclaimer) + + // Ask user if they want step-by-step fix guidance + wantGuide, err := e.promptForFixGuidance(ctx) + if err != nil { + span.SetStatus(codes.Error, "agent.guidance.prompt.failed") + return nil, fmt.Errorf("prompting for fix guidance: %w", err) + } + + if !wantGuide { + span.SetStatus(codes.Ok, "agent.troubleshoot.explain_only") + return actionResult, originalError + } + + // Generate step-by-step fix guidance + guidePrompt := fmt.Sprintf( + `Steps to follow: 1. Use available tools including azd_error_troubleshooting tool to identify this error. 2. Provide only the actionable fix steps as a short numbered list (max 5 steps). Each step should be one sentence. Include exact commands if applicable. DO NOT return JSON. Do not explain the error. Do not perform any file changes. Error details: %s`, errorInput) - case "summarize": - troubleshootPrompt = fmt.Sprintf( - `Steps to follow: - 1. Use available tools including azd_error_troubleshooting tool to identify this error. - 2. Provide a brief summary with two sections: - **Error**: 1-2 sentence explanation of the root cause. - **Fix**: Numbered list of up to 3 actionable steps (one sentence each). - Keep the total response under 10 lines. - DO NOT return JSON. Do not perform any file changes. - Error details: %s`, errorInput) - } - agentOutput, err := azdAgent.SendMessage(ctx, troubleshootPrompt) + guideOutput, err := azdAgent.SendMessage(ctx, guidePrompt) if err != nil { - e.displayAgentResponse(ctx, agentOutput, AIDisclaimer) + e.displayAgentResponse(ctx, guideOutput, AIDisclaimer) span.SetStatus(codes.Error, "agent.send_message.failed") return nil, err } - e.displayAgentResponse(ctx, agentOutput, AIDisclaimer) + e.displayAgentResponse(ctx, guideOutput, AIDisclaimer) } // Ask user if they want to let AI fix the error @@ -226,6 +239,7 @@ func (e *ErrorMiddleware) Run(ctx context.Context, next NextFn) (*actions.Action } if !confirmFix { + if troubleshootScope != "" { if troubleshootScope != "" { span.SetStatus(codes.Ok, "agent.troubleshoot.only") } else { @@ -436,30 +450,18 @@ func (e *ErrorMiddleware) promptTroubleshootingWithConsent(ctx context.Context) return "", fmt.Errorf("failed to load user config: %w", err) } - // Check for saved "always" preferences - scopes := []string{"explain", "guide", "summarize", "skip"} - for _, scope := range scopes { - if val, ok := userConfig.GetString(configPrefix + "." + scope); ok && val == "allow" { - e.console.Message(ctx, output.WithWarningFormat( - "Troubleshooting scope is set to always %q. To change, run %s.\n", - scope, - output.WithHighLightFormat(fmt.Sprintf("azd config unset %s.%s", configPrefix, scope)), - )) - if scope == "skip" { - return "", nil - } - return scope, nil - } + // Check for saved "always skip" preference + if val, ok := userConfig.GetString(configPrefix + ".skip"); ok && val == "allow" { + e.console.Message(ctx, output.WithWarningFormat( + "Troubleshooting is set to always skip. To change, run %s.\n", + output.WithHighLightFormat(fmt.Sprintf("azd config unset %s.skip", configPrefix)), + )) + return "", nil } choices := []*uxlib.SelectChoice{ {Value: "explain", Label: "Explain the error"}, - {Value: "guide", Label: "Provide step-by-step fix guidance"}, - {Value: "summarize", Label: "Summary (explanation + guidance)"}, {Value: "skip", Label: "Skip"}, - {Value: "always.explain", Label: "Always explain the error"}, - {Value: "always.guide", Label: "Always provide fix guidance"}, - {Value: "always.summarize", Label: "Always provide summary"}, {Value: "always.skip", Label: "Always skip"}, } @@ -507,6 +509,33 @@ func (e *ErrorMiddleware) promptTroubleshootingWithConsent(ctx context.Context) return selected, nil } +// promptForFixGuidance asks the user if they want step-by-step fix guidance after seeing the error explanation. +// Returns true if the user wants guidance, false if they want to exit. +func (e *ErrorMiddleware) promptForFixGuidance(ctx context.Context) (bool, error) { + choices := []*uxlib.SelectChoice{ + {Value: "yes", Label: "Yes"}, + {Value: "no", Label: "No, I know what to do (exit agent mode)"}, + } + + selector := uxlib.NewSelect(&uxlib.SelectOptions{ + Message: "Do you want to generate step by step fix guidance?", + Choices: choices, + EnableFiltering: uxlib.Ptr(false), + DisplayCount: len(choices), + }) + + choiceIndex, err := selector.Ask(ctx) + if err != nil { + return false, err + } + + if choiceIndex == nil || *choiceIndex < 0 || *choiceIndex >= len(choices) { + return false, fmt.Errorf("invalid choice selected") + } + + return choices[*choiceIndex].Value == "yes", nil +} + // extractSuggestedSolutions extracts solutions from the LLM response. // It expects a JSON response with the structure: {"analysis": "...", "solutions": ["...", "...", "..."]} // The response may be wrapped in a "text" field by the agent framework: From 71dfc00414b68506deaf3d4fac8e2cca375d334f Mon Sep 17 00:00:00 2001 From: hemarina <104857065+hemarina@users.noreply.github.com> Date: Fri, 27 Feb 2026 18:28:29 -0800 Subject: [PATCH 2/5] Update cli/azd/cmd/middleware/error.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- cli/azd/cmd/middleware/error.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cli/azd/cmd/middleware/error.go b/cli/azd/cmd/middleware/error.go index bf7dc26f682..9a9e54136d6 100644 --- a/cli/azd/cmd/middleware/error.go +++ b/cli/azd/cmd/middleware/error.go @@ -239,7 +239,6 @@ func (e *ErrorMiddleware) Run(ctx context.Context, next NextFn) (*actions.Action } if !confirmFix { - if troubleshootScope != "" { if troubleshootScope != "" { span.SetStatus(codes.Ok, "agent.troubleshoot.only") } else { From 43cae9d4c03c5f422659555b3ead6a3497db1749 Mon Sep 17 00:00:00 2001 From: hemarina <104857065+hemarina@users.noreply.github.com> Date: Fri, 27 Feb 2026 18:28:38 -0800 Subject: [PATCH 3/5] Update cli/azd/cmd/middleware/error.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- cli/azd/cmd/middleware/error.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/azd/cmd/middleware/error.go b/cli/azd/cmd/middleware/error.go index 9a9e54136d6..1958a4ad36a 100644 --- a/cli/azd/cmd/middleware/error.go +++ b/cli/azd/cmd/middleware/error.go @@ -517,7 +517,7 @@ func (e *ErrorMiddleware) promptForFixGuidance(ctx context.Context) (bool, error } selector := uxlib.NewSelect(&uxlib.SelectOptions{ - Message: "Do you want to generate step by step fix guidance?", + Message: "Do you want to generate step-by-step fix guidance?", Choices: choices, EnableFiltering: uxlib.Ptr(false), DisplayCount: len(choices), From db8f91bc529c660ce7338e22609cef5861464e09 Mon Sep 17 00:00:00 2001 From: hemarina <104857065+hemarina@users.noreply.github.com> Date: Fri, 27 Feb 2026 18:29:52 -0800 Subject: [PATCH 4/5] Update cli/azd/cmd/middleware/error.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- cli/azd/cmd/middleware/error.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/cli/azd/cmd/middleware/error.go b/cli/azd/cmd/middleware/error.go index 1958a4ad36a..a5a17f2fe3a 100644 --- a/cli/azd/cmd/middleware/error.go +++ b/cli/azd/cmd/middleware/error.go @@ -162,11 +162,9 @@ func (e *ErrorMiddleware) Run(ctx context.Context, next NextFn) (*actions.Action e.console.Message(ctx, "") troubleshootScope, err := e.promptTroubleshootingWithConsent(ctx) - troubleshootScope, err := e.promptTroubleshootingWithConsent(ctx) if err != nil { span.SetStatus(codes.Error, "agent.consent.failed") return nil, fmt.Errorf("prompting for troubleshooting scope: %w", err) - return nil, fmt.Errorf("prompting for troubleshooting scope: %w", err) } if troubleshootScope != "" { From e5a292b9a7b0fe6b98ce116fc51d4f6d73e7d20b Mon Sep 17 00:00:00 2001 From: hemarina Date: Fri, 27 Feb 2026 18:32:55 -0800 Subject: [PATCH 5/5] remove merge caused duplicate lines --- cli/azd/cmd/middleware/error.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/cli/azd/cmd/middleware/error.go b/cli/azd/cmd/middleware/error.go index bf7dc26f682..77131667494 100644 --- a/cli/azd/cmd/middleware/error.go +++ b/cli/azd/cmd/middleware/error.go @@ -162,11 +162,9 @@ func (e *ErrorMiddleware) Run(ctx context.Context, next NextFn) (*actions.Action e.console.Message(ctx, "") troubleshootScope, err := e.promptTroubleshootingWithConsent(ctx) - troubleshootScope, err := e.promptTroubleshootingWithConsent(ctx) if err != nil { span.SetStatus(codes.Error, "agent.consent.failed") return nil, fmt.Errorf("prompting for troubleshooting scope: %w", err) - return nil, fmt.Errorf("prompting for troubleshooting scope: %w", err) } if troubleshootScope != "" { @@ -239,7 +237,6 @@ func (e *ErrorMiddleware) Run(ctx context.Context, next NextFn) (*actions.Action } if !confirmFix { - if troubleshootScope != "" { if troubleshootScope != "" { span.SetStatus(codes.Ok, "agent.troubleshoot.only") } else { @@ -441,7 +438,7 @@ func promptForErrorHandlingConsent( // promptTroubleshootingWithConsent combines consent and scope selection into a single prompt. // Checks if a saved preference exists (e.g. mcp.errorHandling.troubleshooting.explain). -// Returns the scope ("explain", "fix", "summary") or "" if skipped. +// Returns the scope ("explain") or "" if skipped. func (e *ErrorMiddleware) promptTroubleshootingWithConsent(ctx context.Context) (string, error) { const configPrefix = "mcp.errorHandling.troubleshooting"