Skip to content

Unify host gRPC actionable error transport #7844

@JeffreyCA

Description

@JeffreyCA

Summary

The host-side gRPC error path in cli/azd/internal/grpcserver/server.go relies on wrapErrorWithSuggestion() to append user guidance into the status message string. Recent work improved two adjacent areas:

The host gRPC error shaping path remains a separate, older mechanism that serializes actionable guidance primarily through message text.

Problem

wrapErrorWithSuggestion() mixes auth classification, gRPC status code selection, and "preserve actionable guidance" by concatenating suggestion text into status.Message(). That works but is brittle: it drops Links and any future guidance metadata from *internal.ErrorWithSuggestion, and it keeps auth semantics and actionable UX guidance too tightly coupled in one transport path.

Goal

Reshape host-side gRPC error handling into a single structured mapping path that:

  1. preserves semantic classification, including auth subtype details
  2. preserves actionable guidance (suggestion, links) as typed data, not message text
  3. avoids relying on string concatenation as the transport contract

Proposed approach

  1. Extract the current wrapErrorWithSuggestion() logic from server.go into a dedicated host gRPC error-mapping helper (internal/grpcserver/errors.go) and rename it to something descriptive (e.g. mapHostError).
  2. Keep the PR Provide error suggestions for AADSTS530084 and include auth error details when emitted as ErrorWithSuggestion in telemetry #7797 auth contract:
    • top-level code stays broad (codes.Unauthenticated)
    • auth subtype remains in structured ErrorInfo (azd.auth)
    • azd-local auth conditions use AUTH_*
    • Entra-originated failures preserve the raw AADSTS<code> reason
  3. Introduce a structured gRPC detail (ActionableErrorDetail { suggestion, links }) so *internal.ErrorWithSuggestion can round-trip actionable guidance as typed data.
  4. Teach pkg/azdext consumers (ErrorSuggestion, ErrorMessage, ErrorLinks) to read the new detail in addition to existing LocalError / ServiceError sources.
  5. Stop appending suggestion text into status.Message. The user-facing message becomes ErrorWithSuggestion.Message (or err.Error()); guidance travels separately.
  6. Collapse the duplicated grpcStatusFromError / wrapErrorLinks helpers that exist in both internal/grpcserver and pkg/azdext into single exported azdext versions consumed by both sides.

Scope

  • cli/azd/internal/grpcserver/server.go and a new internal/grpcserver/errors.go
  • new ActionableErrorDetail proto in cli/azd/grpc/proto/errors.proto
  • pkg/azdext consumer parsing and helper consolidation
  • tests covering unary + streaming handlers

Non-goals

Acceptance criteria

  • Host gRPC actionable errors are shaped through one structured mapping path in a dedicated helper file.
  • Auth errors still preserve the azd.auth ErrorInfo contract from PR Provide error suggestions for AADSTS530084 and include auth error details when emitted as ErrorWithSuggestion in telemetry #7797 (AUTH_* for azd-local conditions; AADSTS<code> for Entra-originated failures).
  • *internal.ErrorWithSuggestion round-trips suggestion and links as typed ActionableErrorDetail data, not via string parsing.
  • pkg/azdext extractors (ErrorSuggestion, ErrorMessage, ErrorLinks) surface host-attached actionable detail.
  • Duplicated gRPC status / link helpers are consolidated.
  • Tests cover unary and streaming paths and assert that suggestion text no longer appears in status.Message.

Out of scope (explicitly excluded after discussion)

  • Preserving the Go error chain across the gRPC boundary. gRPC is a serialization boundary; only typed details (auth reason, actionable guidance) cross it. The wrapped chain on the host side is not reconstructible on the extension side and would not be useful if it were.
  • Adding message to ActionableErrorDetail. That would create a fourth message channel duplicating status.Message. The user-facing message belongs in status.Message; the detail carries only what status.Message cannot.
  • String-concatenation compatibility fallback. The previous shim protected no real out-of-tree consumer. Removing it lets the structured path be the single source of truth.

Context

Follow-up to the transport/auth work split across:

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions