Skip to content

User-friendly HTTP error messages for API failures#135

Merged
amilandi merged 3 commits into
mainfrom
amilandin/http-error-messages
Jun 10, 2026
Merged

User-friendly HTTP error messages for API failures#135
amilandi merged 3 commits into
mainfrom
amilandin/http-error-messages

Conversation

@amilandi

@amilandi amilandi commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Summary

ADO tasks https://o365exchange.visualstudio.com/O365%20Core/_workitems/edit/7469877 and https://o365exchange.visualstudio.com/O365%20Core/_workitems/edit/7469904

Adds a centralized HTTP error handler that translates raw status codes into actionable messages with troubleshooting tips. This significantly improves the experience when users hit permission or connectivity issues during install/setup.

Problem

When API calls fail (e.g., user lacks Dataverse read permission), the scripts showed raw tracebacks like:
requests.exceptions.HTTPError: 403 Client Error: Forbidden for url: https://org.crm.dynamics.com/api/data/v9.2/bots?=...

Non-technical users don't know what a 403 means or how to fix it.

Solution

New scripts/http_errors.py module with:

  • APIError class (subclasses requests.HTTPError for backward compat)
  • raise_api_error(response, resource_name, operation, required_role) helper
  • Friendly entity name mapping (bots → "agents", workflows → "cloud flows")
  • Operation-aware messages (read/create/update/delete)
  • Compact technical detail line for debugging

Example output (403 on bots query):

ERROR: You signed in successfully, but your account doesn't have permission to read agents. Tip: Ask your Power Platform admin to assign the Bot Author, Bot Contributor, or System Administrator security role to your account in this environment. Detail: HTTP 403 — GET https://org.crm.dynamics.com/api/data/v9.2/bots

Status codes covered:

Code Message theme
400 Version mismatch / bad query
401 Session expired, re-sign-in
403 Permission denied + role suggestion
404 Resource/solution not deployed
429 Rate limiting + wait guidance
5xx Transient server error + retry advice

Files changed

  • New: scripts/http_errors.py — central error handler
  • scripts/auth.py — replace raise_for_status() with raise_api_error()
  • scripts/discover.py — catch APIError, show friendly output
  • scripts/fetch_and_setup.py — catch APIError in refresh + normal modes

Design decisions

  • FlightCheck clients (graph_client, pp_admin_client) NOT changed — they already handle errors via _error dicts
  • sync_docs.py / sync_samples.py NOT changed — internal tooling, not user-facing during install
  • 401 still uses existing AuthExpiredError (callers depend on it for re-auth)
  • APIError subclasses HTTPError so existing except HTTPError handlers still work

Testing

Verified via unit mock: all status codes produce correct messages, 2xx doesn't raise, except HTTPError still catches APIError.

@amilandi amilandi changed the base branch from amilandin/flightcheck-fetch to main June 9, 2026 22:34
@amilandi amilandi force-pushed the amilandin/http-error-messages branch 2 times, most recently from d44375d to e544da4 Compare June 9, 2026 22:39
Introduces http_errors.py with centralized error handling that translates
raw HTTP status codes into actionable messages with troubleshooting tips.

Status code coverage:
- 400: version mismatch / bad query guidance
- 401: re-sign-in prompt
- 403: permission denied with specific role suggestions
- 404: resource/solution not deployed
- 429: rate limiting with wait guidance
- 5xx: transient server error with retry advice

Changes:
- New: scripts/http_errors.py (APIError class, raise_api_error helper)
- auth.py: replace bare raise_for_status() with raise_api_error()
- discover.py: catch APIError and show friendly terminal output
- fetch_and_setup.py: catch APIError in both refresh and normal modes

The APIError class subclasses requests.HTTPError for backward
compatibility with existing except-HTTPError handlers. Entity set
names are mapped to friendly labels (bots -> agents, workflows ->
cloud flows, etc.) and messages are operation-aware (read/create/
update/delete).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@amilandi amilandi force-pushed the amilandin/http-error-messages branch from e544da4 to e75a3e4 Compare June 9, 2026 22:41
Comment thread solutions/ess-maker-skills/scripts/auth.py
Comment thread solutions/ess-maker-skills/scripts/http_errors.py
Comment thread solutions/ess-maker-skills/scripts/http_errors.py Outdated
Avery Milandin and others added 2 commits June 9, 2026 16:31
1. AuthExpiredError now subclasses APIError (qazizaahirah comment 1).

   Previously 401 raised AuthExpiredError(RuntimeError), which escaped
   the friendly handler in discover.py / fetch_and_setup.py since those
   only catch APIError. Now `except APIError` catches 401s too and
   renders the same friendly message, while `except AuthExpiredError`
   in push.py / FlightCheck still works for re-auth-and-retry.

   All 6 raise sites in auth.py pass response=resp so 401s carry the
   same URL / method / request-id diagnostics as other API errors.
   The 401 friendly message includes `HTTP 401` so existing
   test_preferred_solution.py assertions keep passing.

2. format_for_terminal now reads response.request.method directly
   instead of reverse-engineering the verb from the operation string
   (qazizaahirah comment 3). Removes a 4-branch if/elif chain.

3. Add tests/scripts/test_http_errors.py with 41 tests covering each
   status code, the entity-name mapping, format_for_terminal output,
   raise_api_error behavior, and the AuthExpiredError subclassing
   contract (qazizaahirah comment 2).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@amilandi amilandi merged commit cf093c7 into main Jun 10, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants