Skip to content

fix(pro): friendlier 4xx error rendering and propagate hints to users#2386

Merged
Andriy Knysh (aknysh) merged 5 commits intomainfrom
osterman/pro-error-msgs
May 6, 2026
Merged

fix(pro): friendlier 4xx error rendering and propagate hints to users#2386
Andriy Knysh (aknysh) merged 5 commits intomainfrom
osterman/pro-error-msgs

Conversation

@osterman
Copy link
Copy Markdown
Member

@osterman Erik Osterman (CEO @ Cloud Posse) (osterman) commented May 2, 2026

what

  • Surface the Atmos Pro server's user-facing 4xx fields (errorMessage, errorTag, data.validationErrors[]) directly to the CLI user instead of opaque HTTP 500: API response error: API request failed with status N noise.
  • Render data.validationErrors[] as a bullet list under the headline (with dedupe of trailing server-side concatenations like "failed: A; B").
  • Add a 400 hint linking to settings.pro.* docs and a drift-detection-specific hint (selected by errorTag == DriftDetectionValidationError or substring match on the message).
  • Drop the redundant HTTP <code>: prefix in APIError.Error() for 4xx so output reads UploadInstances: <server message> instead of UploadInstances: HTTP 400: API response error: <server message>.
  • Preserve trace_id on all statuses (including 4xx) so support can correlate user-reported issues.
  • Tolerate both errorMessage (current) and legacy error field on responses; new EffectiveErrorMessage() prefers the former.
  • Replace errors.Join(sentinel, cause) with a small wrapErr helper using the existing errUtils.Build(...).WithCause(...) pattern. Stdlib errors.Join and fmt.Errorf("%w: %w", ...) both produce multi-errors that hide cockroach hint annotations from GetAllHints, which is why the existing 401/403/404/5xx hints never actually rendered to users.
  • Retry behavior unchanged: 4xx remains non-retryable, 401 still refreshes the OIDC token, 5xx still backs off.

why

  • Real user pain: running atmos list instances --upload against a misconfigured stack produced UploadInstances: HTTP 500: API response error: API request failed with status 500 (trace_id: ...) four times in a row. The server actually returned a clean validation message describing the missing drift-detection workflows, but the CLI couldn't surface it (DTO field-name mismatch + lost hints).
  • The atmos-pro server now correctly returns 4xx (not 500) with structured errorMessage and data.validationErrors[] for user-error conditions; this PR is the CLI side of that contract.
  • Hints attached via errUtils.Build(...).WithHint(...) were silently dropped at the outermost wrap layer because errors.Join doesn't expose its children to cockroachErrors.GetAllHints. Users never saw the lightbulb hints the 401/403/404/5xx code paths were already emitting; this PR makes them visible.

references

N/A

Summary by CodeRabbit

  • New Features

    • Richer API error parsing that surfaces structured messages, validation bullets, and drift-detection guidance.
    • Unified error-wrapping semantics that preserve original error hints and improve retry exhaustion reporting.
  • Bug Fixes

    • Cleaner 4xx error text (removed redundant HTTP prefix) while preserving trace IDs.
    • 400 responses are not retried and now surface clearer, deduplicated validation bullets.
    • More consistent error handling and ensured response bodies are closed on error.
  • Tests

    • Expanded tests covering error rendering, drift-detection cases, retry behavior, and response-body/redirect edge cases.

@atmos-pro
Copy link
Copy Markdown
Contributor

atmos-pro Bot commented May 2, 2026

Tip

Atmos Pro  

No affected stacks workflow was detected for this pull request.
If this is expected, no action is needed.
Learn More.

@github-actions github-actions Bot added the size/m Medium size PR label May 2, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 2, 2026

Dependency Review

✅ No vulnerabilities or license issues found.

Scanned Files

None

@osterman Erik Osterman (CEO @ Cloud Posse) (osterman) added the patch A minor, backward compatible change label May 2, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 2, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Converted Pro client multi-error joins to a sentinel-aware wrapper wrapErr(...). Extended Atmos API response DTO with errorTag, legacy error, and data.validationErrors. Validation errors are deduplicated and rendered as bullets; trace_id is preserved. Drift-detection 400s add specific hints; 4xx APIError.Error() drops the redundant HTTP <code>: prefix.

Changes

Pro API client error & DTO overhaul

Layer / File(s) Summary
Data Shape
pkg/pro/dtos/atmos_api_response.go, pkg/pro/dtos/atmos_api_response_test.go
Add AtmosApiResponseData with validationErrors; add ErrorTag, legacy Error, and Data *AtmosApiResponseData to AtmosApiResponse; add EffectiveErrorMessage() preferring errorMessage over legacy error.
Error Wrapping Primitive
pkg/pro/wrap.go, pkg/pro/wrap_test.go
Add wrapErr(sentinel, cause error) error with nil short-circuits and errUtils.Build(...).WithCause(...).Err() wrapping; tests validate sentinel/cause semantics and hint propagation.
Core Error Formatting & Detection
pkg/pro/api_client.go, pkg/pro/api_error.go, pkg/pro/api_error_test.go
Add renderValidationErrors, containsAllValidationErrors, and isDriftDetectionError; logAndReturnProAPIError uses EffectiveErrorMessage() and renders deduped bullets plus trace_id; APIError.Error() omits HTTP <code>: for 4xx; tests updated/added.
Client Wiring & Request Safety
pkg/pro/api_client.go, pkg/pro/api_client_commit.go, pkg/pro/api_client_instances.go, pkg/pro/api_client_instance_status.go, pkg/pro/api_client_commit_test.go
Replace errors.Join(...) with wrapErr(...) across nil-DTO checks, marshal/unmarshal, auth-request creation, HTTP execution, response reads, and OIDC flows. Default HTTPClient when nil and defensively close resp.Body when client.Do returns a non-nil resp alongside an error.
Retry Behavior
pkg/pro/retry.go
doWithRetry returns wrapErr(errUtils.ErrUploadRetryExhausted, err) when exhausted; logging reformatted to structured calls.
Request/Response Handling & Tests
pkg/pro/api_client_instances_test.go, pkg/pro/api_client_test.go, pkg/pro/api_client_exchange_oidc_token_test.go
Add/extend tests: UploadInstances 400 non-retry behavior and single-request assertion; redirect error body-closure tests; buildProAPIError and handleAPIResponse tests for drift-detection hints, rendered validation bullets, dedupe, trace_id preservation, and OIDC exchange error handling.
sequenceDiagram
  participant Client as Atmos CLI
  participant OIDC as GitHub OIDC
  participant Pro as Atmos Pro API
  participant HTTP as net/http Client

  Client->>OIDC: getGitHubOIDCToken()
  OIDC-->>Client: OIDC token / error
  Client->>HTTP: exchangeOIDCTokenForAtmosToken (POST)
  HTTP-->>Pro: request
  Pro-->>HTTP: response (200/400/5xx)
  HTTP-->>Client: response body
  Client->>Client: handleAPIResponse -> buildProAPIError / renderValidationErrors
  alt retryable (5xx)
    Client->>Client: doWithRetry / token refresh flows
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • aknysh
  • milldr
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 70.27% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly reflects the main changes: improving 4xx error rendering and preserving hints for users.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch osterman/pro-error-msgs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/pro/api_client_commit.go`:
- Around line 61-64: The early return after calling client.Do(req) can leak
connections because resp may be non-nil when doErr is set; update the Do error
path in the block where resp, doErr := client.Do(req) is handled so that if resp
!= nil you close resp.Body before returning (e.g., call resp.Body.Close() or
ensure a defer is set immediately after verifying resp) and then return
wrapErr(errUtils.ErrFailedToMakeRequest, doErr); reference the resp, doErr
variables and the wrapErr/errUtils.ErrFailedToMakeRequest usage to locate the
change.

In `@pkg/pro/api_client_instances.go`:
- Around line 93-96: The request path calls client.Do(req) and returns on error
but doesn't close a non-nil resp body, leaking connections; update the error
branch around client.Do(req) so that if doErr != nil and resp != nil you fully
drain and close resp.Body (e.g., io.Copy(io.Discard, resp.Body);
resp.Body.Close()) before returning wrapErr(errUtils.ErrFailedToMakeRequest,
doErr), and keep the existing defer resp.Body.Close() for the successful path;
ensure io is imported if using io.Discard.

In `@pkg/pro/api_client_test.go`:
- Around line 764-766: The comment lines in the test
TestBuildProAPIError_400DriftDetection contain multi-line doc comments that do
not end with periods; update those comment blocks (and the other mentioned
blocks at ranges 793-794, 806-808, 841-842, 869-870, 913-914 in the same file)
so each sentence/line ends with a period and reflow wrapped lines as needed to
follow the repo rule; locate the comments adjacent to the
TestBuildProAPIError_400DriftDetection function name and edit the comment text
to add trailing periods and adjust line breaks so every line ends cleanly with a
period.

In `@pkg/pro/api_client.go`:
- Around line 106-112: Deduplicate the entries in data.validationErrors before
building the bullet list: create a seen map[string]struct{} and iterate the
existing validationErrors slice, skipping any value already in seen and adding
new values to seen (or alternatively build a unique slice preserving
first-occurrence order), then use that deduplicated slice in the block that
writes to the strings.Builder (the code around the variables validationErrors
and b). Replace the current direct loop over validationErrors with a loop over
the deduplicated collection so duplicate bullets are not emitted.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b20590fb-cffa-4d52-bc56-1ddaa0c2033e

📥 Commits

Reviewing files that changed from the base of the PR and between a86eda5 and 2182965.

📒 Files selected for processing (13)
  • pkg/pro/api_client.go
  • pkg/pro/api_client_commit.go
  • pkg/pro/api_client_exchange_oidc_token_test.go
  • pkg/pro/api_client_instance_status.go
  • pkg/pro/api_client_instances.go
  • pkg/pro/api_client_instances_test.go
  • pkg/pro/api_client_test.go
  • pkg/pro/api_error.go
  • pkg/pro/api_error_test.go
  • pkg/pro/dtos/atmos_api_response.go
  • pkg/pro/dtos/atmos_api_response_test.go
  • pkg/pro/retry.go
  • pkg/pro/wrap.go

Comment thread pkg/pro/api_client_commit.go
Comment thread pkg/pro/api_client_instances.go
Comment thread pkg/pro/api_client_test.go
Comment thread pkg/pro/api_client.go
atmos-pro[bot]
atmos-pro Bot previously approved these changes May 2, 2026
Copy link
Copy Markdown
Contributor

@atmos-pro atmos-pro Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There were no affected stacks, therefore this is approved by Atmos Pro.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 2, 2026

Codecov Report

❌ Patch coverage is 82.94574% with 22 lines in your changes missing coverage. Please review.
✅ Project coverage is 78.15%. Comparing base (acc1016) to head (2fcdc6f).

Files with missing lines Patch % Lines
pkg/pro/api_client.go 86.25% 10 Missing and 1 partial ⚠️
pkg/pro/api_client_instance_status.go 20.00% 4 Missing ⚠️
pkg/pro/api_client_instances.go 60.00% 4 Missing ⚠️
pkg/pro/api_client_commit.go 66.66% 3 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #2386      +/-   ##
==========================================
+ Coverage   78.11%   78.15%   +0.04%     
==========================================
  Files        1092     1094       +2     
  Lines      103308   103377      +69     
==========================================
+ Hits        80697    80793      +96     
+ Misses      18183    18152      -31     
- Partials     4428     4432       +4     
Flag Coverage Δ
unittests 78.15% <82.94%> (+0.04%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
pkg/pro/api_error.go 100.00% <100.00%> (ø)
pkg/pro/dtos/atmos_api_response.go 100.00% <100.00%> (ø)
pkg/pro/retry.go 100.00% <100.00%> (ø)
pkg/pro/wrap.go 100.00% <100.00%> (ø)
pkg/pro/api_client_commit.go 88.88% <66.66%> (+0.42%) ⬆️
pkg/pro/api_client_instance_status.go 67.74% <20.00%> (ø)
pkg/pro/api_client_instances.go 76.78% <60.00%> (+7.69%) ⬆️
pkg/pro/api_client.go 91.45% <86.25%> (+0.59%) ⬆️

... and 5 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
pkg/pro/api_client.go (1)

320-323: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Close non-nil response bodies on the remaining Do error paths.

These branches still return early without closing resp.Body when http.Client.Do(...) yields both a response and an error. That is the same leak pattern you already fixed in the commit and instances paths.

Suggested fix pattern.
 	resp, err := client.Do(req)
 	if err != nil {
+		if resp != nil && resp.Body != nil {
+			_ = resp.Body.Close()
+		}
 		return wrapErr(errUtils.ErrFailedToMakeRequest, err)
 	}
In Go's net/http package, can http.Client.Do return both a non-nil *http.Response and a non-nil error, and should callers close Response.Body in that case?

Also applies to: 349-352, 601-605, 664-667

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/pro/api_client.go` around lines 320 - 323, The early returns after
calling c.HTTPClient.Do(req) can leak when Do returns both a non-nil resp and a
non-nil error; update each site that calls c.HTTPClient.Do(req) so that on doErr
!= nil you check if resp != nil, drain (io.Copy to io.Discard) and close
resp.Body before returning wrapErr(errUtils.ErrFailedToMakeRequest, doErr), and
add an import for io if needed; apply the same change to the other occurrences
of c.HTTPClient.Do(req) in this file so every error path closes resp.Body.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/pro/api_client.go`:
- Around line 99-120: The code trims and dedupes validationErrors only when
emitting the list, but containsAllValidationErrors(headlineTail,
validationErrors) is called on the raw slice so blanks and whitespace-only
duplicates can cause the inline ": A" tail to remain and duplicate entries to
render; normalize the slice first (trim each entry, skip empty strings, dedupe
into a new []string or map) and use that normalized slice both when calling
containsAllValidationErrors and when iterating to build the output (replace the
current loop and seen map logic), ensuring headline mutation and the builder
loop operate off the same cleaned set.
- Around line 80-84: The trace_id suffix is being appended to errorMsg after
calling renderValidationErrors, which makes it appear as part of the last
validation bullet; update the logic in the function that builds the error
message (the code using renderValidationErrors, errorMsg, validationErrors and
traceID) so trace_id is either attached before calling renderValidationErrors or
appended as its own separate line (e.g., add a "\n" or separate bullet) rather
than directly concatenating to errorMsg after renderValidationErrors returns;
ensure you modify the code paths that reference traceID and
renderValidationErrors to preserve existing formatting and keep trace_id
visually separate from the validation bullets.

---

Duplicate comments:
In `@pkg/pro/api_client.go`:
- Around line 320-323: The early returns after calling c.HTTPClient.Do(req) can
leak when Do returns both a non-nil resp and a non-nil error; update each site
that calls c.HTTPClient.Do(req) so that on doErr != nil you check if resp !=
nil, drain (io.Copy to io.Discard) and close resp.Body before returning
wrapErr(errUtils.ErrFailedToMakeRequest, doErr), and add an import for io if
needed; apply the same change to the other occurrences of c.HTTPClient.Do(req)
in this file so every error path closes resp.Body.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4853b58a-1031-404c-abdf-c05dc1006e8c

📥 Commits

Reviewing files that changed from the base of the PR and between 2182965 and e639d23.

📒 Files selected for processing (4)
  • pkg/pro/api_client.go
  • pkg/pro/api_client_commit.go
  • pkg/pro/api_client_instances.go
  • pkg/pro/api_client_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/pro/api_client_test.go

Comment thread pkg/pro/api_client.go
Comment thread pkg/pro/api_client.go Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
pkg/pro/wrap_test.go (1)

16-43: ⚡ Quick win

Please consolidate these three scenarios into a table-driven test.

Nice coverage here, but this block is testing one behavior family with three variants. Converting to table-driven form will align with repo test conventions and reduce duplication.

As per coding guidelines "Use table-driven tests for testing multiple scenarios in Go."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/pro/wrap_test.go` around lines 16 - 43, Replace the three separate tests
with a single table-driven test (e.g., TestWrapErr_TableDriven) that defines
cases with fields: name, sentinel, cause, expectError, expectSame,
expectIsSentinel, expectIsCause, expectHintsContains; iterate cases with t.Run
and for each call wrapErr(sentinel, cause), assert require.Error(t, got) when
expectError is true, use assert.Same(t, ...) only when expectSame is true (for
the nil sentinel case), use assert.True(t, errors.Is(got,
errUtils.ErrFailedToMakeRequest)) and assert.True(t, errors.Is(got, cause))
according to expectIsSentinel/expectIsCause, and check
cockroachErrors.GetAllHints(got) contains the expected hint when
expectHintsContains is set (for the WithHint case); keep references to wrapErr,
errUtils.ErrFailedToMakeRequest, cockroachErrors.WithHint and
cockroachErrors.GetAllHints to locate code.
pkg/pro/api_client_commit_test.go (1)

163-186: ⚡ Quick win

TestSendCommitRequest_RedirectErrorClosesBody currently validates error wrapping, not body-close behavior.

If the resp.Body.Close() guard regresses, this test would still pass. Please either assert the close side effect (preferred) or rename the test to match what it verifies today.

As per coding guidelines, "**/*_test.go: Test behavior, not implementation; never test stub functions; avoid tautological tests."

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@pkg/pro/wrap_test.go`:
- Around line 16-43: Replace the three separate tests with a single table-driven
test (e.g., TestWrapErr_TableDriven) that defines cases with fields: name,
sentinel, cause, expectError, expectSame, expectIsSentinel, expectIsCause,
expectHintsContains; iterate cases with t.Run and for each call
wrapErr(sentinel, cause), assert require.Error(t, got) when expectError is true,
use assert.Same(t, ...) only when expectSame is true (for the nil sentinel
case), use assert.True(t, errors.Is(got, errUtils.ErrFailedToMakeRequest)) and
assert.True(t, errors.Is(got, cause)) according to
expectIsSentinel/expectIsCause, and check cockroachErrors.GetAllHints(got)
contains the expected hint when expectHintsContains is set (for the WithHint
case); keep references to wrapErr, errUtils.ErrFailedToMakeRequest,
cockroachErrors.WithHint and cockroachErrors.GetAllHints to locate code.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5def6968-7731-4bd2-be7d-5991a88581a2

📥 Commits

Reviewing files that changed from the base of the PR and between e639d23 and 64b3528.

📒 Files selected for processing (4)
  • pkg/pro/api_client.go
  • pkg/pro/api_client_commit_test.go
  • pkg/pro/api_client_instances_test.go
  • pkg/pro/wrap_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • pkg/pro/api_client_instances_test.go
  • pkg/pro/api_client.go

atmos-pro[bot]
atmos-pro Bot previously approved these changes May 5, 2026
Copy link
Copy Markdown
Contributor

@atmos-pro atmos-pro Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There were no affected stacks, therefore this is approved by Atmos Pro.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pkg/pro/retry.go (1)

82-97: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep the 401 refresh-failure path on wrapErr(...) too.

This block now uses wrapErr(...) for retry exhaustion, but the earlier refreshErr branch still returns errors.Join(err, refreshErr). That means a failed token refresh can fall back to the old multi-error shape and lose the hint/cause annotations this PR is trying to preserve in CLI output. Please route that branch through the same wrapper path as well.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/pro/retry.go` around lines 82 - 97, The refreshErr branch currently
returns errors.Join(err, refreshErr) which strips the wrapper annotations;
change that branch to return the same wrapped error form as the retry-exhausted
path by calling wrapErr with the refresh sentinel as the wrapper and the
original err as the cause (i.e. replace errors.Join(err, refreshErr) with
wrapErr(refreshErr, err) in the block that checks refreshErr), keeping the
existing logging and other behavior around attempt, cfg.maxRetries, refreshErr,
and err.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/pro/api_client.go`:
- Around line 101-123: The headline truncation logic should only run when there
are non-empty normalized validation errors; update the conditional around the
tail-stripping block that uses headline, normalized, and
containsAllValidationErrors so it first checks len(normalized) > 0, and also
consider using strings.LastIndex(headline, ": ") (instead of strings.Index) to
locate the final colon so earlier colon-delimited context in headline is not
lost. Modify the code that computes idx and the if that calls
containsAllValidationErrors(tail, normalized) accordingly (variables/functions:
headline, normalized, containsAllValidationErrors, strings.Index/LastIndex).

---

Outside diff comments:
In `@pkg/pro/retry.go`:
- Around line 82-97: The refreshErr branch currently returns errors.Join(err,
refreshErr) which strips the wrapper annotations; change that branch to return
the same wrapped error form as the retry-exhausted path by calling wrapErr with
the refresh sentinel as the wrapper and the original err as the cause (i.e.
replace errors.Join(err, refreshErr) with wrapErr(refreshErr, err) in the block
that checks refreshErr), keeping the existing logging and other behavior around
attempt, cfg.maxRetries, refreshErr, and err.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: acb2e036-9987-4d33-8045-73f8b1f068af

📥 Commits

Reviewing files that changed from the base of the PR and between 64b3528 and 92f57ae.

📒 Files selected for processing (3)
  • pkg/pro/api_client.go
  • pkg/pro/api_client_instances.go
  • pkg/pro/retry.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/pro/api_client_instances.go

Comment thread pkg/pro/api_client.go Outdated
atmos-pro[bot]
atmos-pro Bot previously approved these changes May 5, 2026
Copy link
Copy Markdown
Contributor

@atmos-pro atmos-pro Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There were no affected stacks, therefore this is approved by Atmos Pro.

Improve developer experience when the Atmos Pro API returns a 4xx by
surfacing the server's structured `errorMessage`, `errorTag`, and
`data.validationErrors[]` in a way users can actually act on.

- DTO: tolerate both `errorMessage` and legacy `error`; capture
  `errorTag` and `data.validationErrors`; add `EffectiveErrorMessage()`.
- Render `data.validationErrors` as a bullet list under the headline
  (with dedupe of trailing concatenations).
- Add a 400 hint pointing at `settings.pro.*` configuration plus a
  drift-detection-specific hint when the server's `errorTag` or
  message identifies that subsystem.
- Drop the redundant "HTTP <code>:" prefix in `APIError.Error()` for
  4xx responses — the server's message is already user-facing, so
  output reads `UploadInstances: <message>` instead of
  `UploadInstances: HTTP 400: API response error: <message>`.
- Preserve `trace_id` on all statuses (including 4xx) so support can
  still correlate user-reported issues server-side.
- Replace `errors.Join(sentinel, cause)` with a small `wrapErr` helper
  using the existing `errUtils.Build(...).WithCause(...)` pattern.
  `errors.Join` (and `fmt.Errorf("%w: %w", ...)`) produce multi-errors
  that hide cockroach hint annotations from `GetAllHints`, so users
  never saw the lightbulb hints. `wrapErr` re-attaches hints on the
  outer layer so the CLI renderer surfaces them.
- Retry behavior unchanged: 4xx still non-retryable, 401 still
  refreshes the OIDC token, 5xx still backs off.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Address CodeRabbit review feedback: collapse duplicate or whitespace-only
validation errors into a single bullet, and close any non-nil response
body returned alongside an error from http.Client.Do (e.g., redirect
failures) in the commit and instances upload paths.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Address CodeRabbit review on the prior commit:

- Put trace_id on its own line so it can't be misread as the last
  validation bullet when both are rendered.
- Normalize the validation-error slice once (trim, drop empties, dedupe)
  and reuse it for both the headline tail-match and the bullet rendering,
  so inputs like ["A", " A "] strip the inline tail consistently.
- Add direct tests for wrapErr and the defensive resp.Body close path
  triggered by CheckRedirect failures, lifting the patch coverage above
  the 80% target.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
CI's pre-commit go-fumpt hook (gofumpt v0.10.0) reformats long
log.Error/log.Warn/log.Debug calls to put the format string on its own
line. Apply the same formatting locally so the hook passes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Address CodeRabbit feedback on renderValidationErrors:

- Guard the tail-stripping path with len(normalized) > 0 so a server
  reply containing only blank/whitespace validationErrors no longer
  truncates the headline despite rendering no bullets.
- Switch the colon search from strings.Index to strings.LastIndex so
  earlier colon-delimited context (e.g. "Component vpc: validation
  failed: A; B") is preserved when stripping the duplicated tail.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@atmos-pro atmos-pro Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There were no affected stacks, therefore this is approved by Atmos Pro.

@aknysh Andriy Knysh (aknysh) merged commit c07f463 into main May 6, 2026
58 checks passed
@aknysh Andriy Knysh (aknysh) deleted the osterman/pro-error-msgs branch May 6, 2026 03:00
@atmos-pro
Copy link
Copy Markdown
Contributor

atmos-pro Bot commented May 6, 2026

Note

Atmos Pro  

Waiting for your GitHub Actions workflow to upload affected stacks.
Learn More.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 7, 2026

These changes were released in v1.218.0-rc.1.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

patch A minor, backward compatible change size/m Medium size PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants