Skip to content

Treat empty Hive-Engine rewards response as no rewards, not an error#55

Merged
feruzm merged 2 commits into
mainfrom
fix/engine-rewards-empty-not-error
Jul 13, 2026
Merged

Treat empty Hive-Engine rewards response as no rewards, not an error#55
feruzm merged 2 commits into
mainfrom
fix/engine-rewards-empty-not-error

Conversation

@feruzm

@feruzm feruzm commented Jul 13, 2026

Copy link
Copy Markdown
Member

FetchEngineRewards treated "this account has no Hive-Engine rewards" as a failure.

The rewards upstream answers with an empty object for an account with no Hive-Engine activity. The handler threw on that (and on a non-object response), fell into the catch, logged failed to get unclaimed engine rewards No rewards data returned, and returned an empty array. The empty array was the right answer — but it was reached via an exception and an error log, on every request for such an account. That is a hot-path write on the portfolio endpoints, against invariant 6.

This returns the empty result directly. An empty object now falls through the existing loop to an empty array, which is what the catch produced anyway.

Not a behavior change:

  • non-object response: was throw -> catch -> empty array. Now: empty array.
  • empty object: was throw -> catch -> empty array. Now: loop over zero entries -> empty array.
  • non-empty object: unchanged.
  • a genuine failure (network, malformed payload) still reaches the catch and is still logged.

Verified by running this build and main side by side against the same upstreams and diffing /wallet-api/portfolio-v2: responses are byte-identical for accounts with no engine rewards, and the error log goes from one line per request to none. Accounts that do have rewards are byte-identical too, apart from live chain values (HP balance, APR, staked token accrual) that move between calls. Test suite green.

An account with no Hive-Engine activity gets an empty object back from the
rewards upstream. Both that and a non-object response threw into the catch,
which logged an error and returned an empty array. The empty result was
correct; the error log was not, and it fired on every such request.

Return the empty array directly instead. Genuine failures still reach the
catch and are still logged. Responses are unchanged.
@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@feruzm, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 32 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5a095aa7-0f04-4ba3-91af-d04edebc37ed

📥 Commits

Reviewing files that changed from the base of the PR and between 80fa164 and 2e24882.

📒 Files selected for processing (1)
  • dotnet/EcencyApi/Handlers/WalletApi.Engine.cs
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/engine-rewards-empty-not-error

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.

@greptile-apps

greptile-apps Bot commented Jul 13, 2026

Copy link
Copy Markdown

Greptile Summary

This PR changes how Hive-Engine rewards responses are handled.

  • Empty reward objects now return an empty rewards array directly.
  • Non-object reward payloads now throw into the existing catch path.
  • Non-empty reward objects still flow through the existing conversion loop.

Confidence Score: 4/5

This is close, but one rewards error path should be fixed before merging.

  • Non-object malformed payloads now reach the catch path.
  • Empty reward objects now avoid noisy error logging.
  • Object-shaped upstream error payloads can still be returned as an empty rewards result with no log.

dotnet/EcencyApi/Handlers/WalletApi.Engine.cs

Important Files Changed

Filename Overview
dotnet/EcencyApi/Handlers/WalletApi.Engine.cs Updates Hive-Engine reward handling so empty objects are treated as no rewards while non-object payloads still enter the error path.

Fix All in Claude Code

Reviews (2): Last reviewed commit: "Keep logging non-object rewards payloads" | Re-trigger Greptile

Comment thread dotnet/EcencyApi/Handlers/WalletApi.Engine.cs Outdated
A non-object body means the rewards upstream is degraded (unparseable body, or
valid JSON that is not an object), which is worth a log line. Only the empty
object - the normal answer for an account with no Hive-Engine activity - is
silent now.
Comment on lines 185 to +186
if (response.Json is not JsonObject obj)
throw new Exception("No rewards data returned");

var rawValues = obj.Select(kv => kv.Value).ToList();
if (rawValues.Count == 0) throw new Exception("No rewards data returned");
throw new Exception("Unexpected rewards payload");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Object Errors Still Disappear

This check only sends non-object payloads through the catch path. If the rewards service returns an object-shaped error body like { "error": "rate limit" } or { "message": "Internal Server Error" }, it passes this branch, the loop feeds the string value into HiveEngine.ConvertRewardsStatus, and pendingToken stays null so the result is filtered down to an empty rewards array. Callers then receive “no rewards” with no error log, even though the upstream response was degraded. Please validate the expected reward-entry shape, or the upstream status, while keeping only an actually empty object as the no-rewards case.

Fix in Claude Code

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This one is pre-existing behavior, not something this PR introduces — main does exactly the same thing with an object-shaped error body.

main's only guard was rawValues.Count == 0. A body like {"error": "rate limit"} has one key, so it never hit that throw either: it went through the same loop, ConvertRewardsStatus returned nulls (JsVal.Prop on a non-object yields null, so pendingToken is null and pendingRewards is NaN — no exception), the pendingToken is > 0 filter dropped it, and the caller got an empty array with no log. Identical before and after.

The only guard this PR removes is the Count == 0 one, i.e. the genuinely-empty object. The path you're describing is untouched by the diff, so there is nothing here to regress.

Worth saying the underlying observation is fair: an object-shaped error body does read as "no rewards". But that has always been true, and closing it means validating the reward-entry shape or the upstream status — a behavior change beyond a log-noise fix, and one that needs to be weighed against the response-parity contract this service maintains. Leaving it out of scope here rather than smuggling it in.

@feruzm feruzm merged commit 94ecf24 into main Jul 13, 2026
5 checks passed
@feruzm feruzm deleted the fix/engine-rewards-empty-not-error branch July 13, 2026 10:40
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.

1 participant