Extension framework - propagate error suggestions from event handlers and custom service targets#7791
Extension framework - propagate error suggestions from event handlers and custom service targets#7791JeffreyCA wants to merge 2 commits intoAzure:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR improves azd’s extension framework error propagation so extension-authored structured errors (including Suggestion) survive event handler/custom service target boundaries and aren’t overridden by azd’s YAML-based suggestion pipeline.
Changes:
- Preserve original error types when raising a single event handler error (instead of flattening to a string).
- Extend the event gRPC contract to include structured
ExtensionErrordetails in handler status messages and unwrap them on the host side. - Skip azd’s YAML error-suggestion pipeline when an extension error already includes a suggestion; add a JSON-output regression test for structured errors.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| cli/azd/pkg/ext/event_dispatcher.go | Preserves single handler error types; aggregates suggestions from extension errors. |
| cli/azd/pkg/azdext/event_manager.go | Sends structured ExtensionError back to host on handler failure. |
| cli/azd/pkg/azdext/event.pb.go | Regenerated protobuf output to include ExtensionError fields. |
| cli/azd/internal/grpcserver/event_service.go | Unwraps structured handler errors coming back from extensions. |
| cli/azd/grpc/proto/event.proto | Adds ExtensionError fields to handler status messages. |
| cli/azd/cmd/middleware/error.go | Skips YAML suggestion pipeline when extension suggestion is present. |
| cli/azd/cmd/middleware/ux_test.go | Adds coverage for JSON output shape when returning ErrorWithSuggestion. |
Comments suppressed due to low confidence (1)
cli/azd/pkg/ext/event_dispatcher.go:157
- When aggregating multiple handler errors, the returned ErrorWithSuggestion sets Suggestion but leaves Message empty. Since Message is part of the structured error contract (and is asserted in other tests), consider setting Message to a user-friendly summary (or the combined error text) so JSON/UX output isn’t missing the explanatory field.
if len(suggestions) > 0 {
return &internal.ErrorWithSuggestion{
Err: combinedErr,
Suggestion: strings.Join(suggestions, "\n"),
}
…service targets Extension errors with suggestions (LocalError / ServiceError) raised from project/service event handlers and custom service targets were losing their structured information by the time they reached middleware, so the host fell back to generic error rendering and the YAML error-suggestion pipeline overrode the extension's specific guidance. - proto: add ExtensionError to ProjectHandlerStatus and ServiceHandlerStatus - event_manager: WrapError on the extension side when reporting failed status - event_service: UnwrapError on the host side to restore typed errors - ext/event_dispatcher: when only a single handler error occurs, return it unchanged so structured types survive for downstream errors.As; also pick up suggestions from azdext extension errors in the multi-error path - cmd/middleware/error: short-circuit the YAML pipeline when the error already carries an extension-supplied suggestion - cmd/middleware/ux_test: regression test that ErrorWithSuggestion in JSON output mode does not emit a leading consoleMessage envelope Fixes Azure#7706 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1e64bfc to
f1de4cf
Compare
Address PR Azure#7791 review feedback: - pkg/ext/event_dispatcher.go: fix "their" -> "there" typo in comment - pkg/azdext/event_manager_test.go: assert that the failed-status reply carries a populated ExtensionError (Message/Suggestion/LocalErrorDetail) so the host can unwrap it back into a typed *LocalError - cmd/middleware/error_test.go: add regression test that an azdext.LocalError with a Suggestion bypasses the YAML error-suggestion pipeline (using a message that would otherwise match the QuotaExceeded rule) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
jongio
left a comment
There was a problem hiding this comment.
Approach looks right. The end-to-end story (extension -> ExtensionError proto -> UnwrapError on host -> middleware bypass when Suggestion is set) is coherent and the existing Copilot thread captures most of the technical depth. Two things worth tightening before merge.
| // This lets structured errors (e.g. LocalError, ErrorWithSuggestion) | ||
| // flow through to middleware without losing type information. | ||
| if len(handlerErrors) == 1 { | ||
| return handlerErrors[0] |
There was a problem hiding this comment.
The new len(handlerErrors) == 1 fast path is the load-bearing change that lets a typed *LocalError (or *ErrorWithSuggestion) survive intact up to the middleware. Without it the unwrapped extension error would get re-stringified by the multi-error joining below and the whole fix collapses. Test_Multiple_Errors_With_Suggestions only covers the multi-error branch, so this single-error preservation is currently untested at the dispatcher layer.
Worth adding a focused test alongside it, e.g. Test_Single_Error_Preserves_Type, that registers one handler returning a *LocalError (or *ErrorWithSuggestion), calls RaiseEvent, then asserts errors.AsType[*azdext.LocalError](err) succeeds and Suggestion is preserved. That locks in the contract and stops a future refactor from silently regressing #7706.
| suggestions = append(suggestions, errWithSuggestion.Suggestion) | ||
| } | ||
| if s := azdext.ErrorSuggestion(err); s != "" { | ||
| suggestions = append(suggestions, s) |
There was a problem hiding this comment.
Minor: these two suggestion checks aren't mutually exclusive. ErrorWithSuggestion has an Unwrap() method, so if a handler ever returns &ErrorWithSuggestion{Err: localErr, Suggestion: "x"} where localErr is a *LocalError with its own Suggestion: "y", both branches fire and you'll append both strings into suggestions for the same underlying failure.
Probably rare today but cheap to defuse with else if:
| suggestions = append(suggestions, s) | |
| if errWithSuggestion, ok := errors.AsType[*internal.ErrorWithSuggestion](err); ok && | |
| errWithSuggestion.Suggestion != "" { | |
| suggestions = append(suggestions, errWithSuggestion.Suggestion) | |
| } else if s := azdext.ErrorSuggestion(err); s != "" { | |
| suggestions = append(suggestions, s) | |
| } |
Keeps one suggestion per error and matches the precedence ordering the rest of the file uses (azd-wrapped first, then extension-typed).
Azure Dev CLI Install InstructionsInstall scriptsMacOS/Linux
bash: pwsh: WindowsPowerShell install MSI install Standalone Binary
MSI
Documentationlearn.microsoft.com documentationtitle: Azure Developer CLI reference
|
Resolves #7706
With this PR, extension-authored error suggestions now flow through to the user when an error is raised from a project/service event handler or a custom service target.
Previously, the rich
LocalError/ServiceErrorreturned by the extension was flattened into a plain string before reaching the host's middleware, so:Suggestiontext was dropped, and