Skip to content

Fix core-state poll logs and rewards timeout UX#1321

Open
jwalin-shah wants to merge 3 commits into
tinyhumansai:mainfrom
jwalin-shah:codex/SYM-209-core-state-rewards-timeout
Open

Fix core-state poll logs and rewards timeout UX#1321
jwalin-shah wants to merge 3 commits into
tinyhumansai:mainfrom
jwalin-shah:codex/SYM-209-core-state-rewards-timeout

Conversation

@jwalin-shah
Copy link
Copy Markdown
Contributor

@jwalin-shah jwalin-shah commented May 7, 2026

Summary

  • Rate-limit core-state poll warnings so repeated bootstrap failures log at useful checkpoints instead of every poll.
  • Keep detailed debug('core-state') failure diagnostics for development without console warning spam.
  • Add a shorter /rewards/me request timeout and normalize timeout-style failures into an actionable retry message.
  • Preserve rewards success normalization and backend failure behavior with focused tests.

Problem

  • Startup can emit repeated [core-state] poll failed warnings while the core state endpoint is unavailable.
  • /rewards/me timeout-style failures can surface as low-level network/client errors instead of a quiet, recoverable Rewards state.

Solution

  • Added shouldWarnForBootstrapFailure() and use it to warn on attempts 1, 5, then every 5 failures thereafter while debug logs still include every failed attempt.
  • Wrapped rewardsApi.getMyRewards() request/failure handling to apply a 15s timeout and map timeout/abort/ERR_TIMED_OUT messages to: “Rewards request timed out. Please check your connection and try again.”
  • Updated provider and rewards API tests for warning checkpoint behavior, existing success behavior, backend failures, and timeout mapping.

Submission Checklist

  • Tests added or updated (happy path + at least one failure / edge case) per docs/TESTING-STRATEGY.md
  • Diff coverage ≥ 80% — CI diff-cover is authoritative and is running on this PR; focused changed-line tests passed locally.
  • Coverage matrix updated — N/A: behavior-only change to existing core-state/rewards flows.
  • All affected feature IDs from the matrix are listed in the PR description under ## Related — N/A: no feature ID row changed.
  • No new external network dependencies introduced (mock backend used per docs/TESTING-STRATEGY.md)
  • Manual smoke checklist updated if this touches release-cut surfaces — N/A: no release smoke checklist behavior changed.
  • Linked issue closed via Closes #NNN in the ## Related section

Impact

  • Runtime impact is limited to the React app provider and rewards API client.
  • No Rust/core behavior changes.
  • Rewards timeout failures remain recoverable through the existing Rewards retry UI.

Related


AI Authored PR Metadata (required for Codex/Linear PRs)

Linear Issue

Commit & Branch

  • Branch: codex/SYM-209-core-state-rewards-timeout
  • Commit SHA: f113824af973abb8977bbd92cd8ae45ed69da3bb

Validation Run

  • pnpm --filter openhuman-app format:check (via pre-push hook; passed)
  • pnpm typecheck / pnpm --filter openhuman-app compile (passed)
  • Focused tests: pnpm --dir app exec vitest run src/providers/__tests__/CoreStateProvider.test.tsx src/services/api/__tests__/rewardsApi.test.ts --config test/vitest.config.ts (13 passed)
  • Rust fmt/check (if changed): cargo fmt --manifest-path ../Cargo.toml --all --check (via pre-push hook; passed); cargo check --manifest-path src-tauri/Cargo.toml (via pre-push hook; passed with pre-existing warnings)
  • Tauri fmt/check (if changed): cargo fmt --manifest-path src-tauri/Cargo.toml --all --check and cargo check --manifest-path src-tauri/Cargo.toml (via pre-push hook; passed with pre-existing warnings)

Validation Blocked

  • command: N/A
  • error: N/A
  • impact: N/A

Behavior Changes

  • Intended behavior change: core-state polling warnings are deduped/rate-limited; rewards timeout errors become actionable retry guidance.
  • User-visible effect: Rewards remains in a quiet fallback state with existing Try again recovery when /rewards/me times out.

Parity Contract

  • Legacy behavior preserved: rewards success payload normalization and backend failure propagation remain covered.
  • Guard/fallback/dispatch parity checks: timeout/abort/network timeout mapping added without changing core RPC or onboarding/OAuth flows.

Duplicate / Superseded PR Handling

  • Duplicate PR(s): none found for this fresh branch.
  • Canonical PR: this PR.
  • Resolution (closed/superseded/updated): N/A

Summary by CodeRabbit

  • Bug Fixes

    • Improved error handling for rewards loading with a clearer, standardized timeout error message.
    • Reduced excessive bootstrap warnings; warnings now occur only at specific retry checkpoints.
  • Tests

    • Enhanced tests for timeout handling and warning rate-limiting behavior.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 7, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ec9e88ae-3de8-4d96-a7bb-81dd5a8e59c0

📥 Commits

Reviewing files that changed from the base of the PR and between f113824 and 3af380b.

📒 Files selected for processing (3)
  • app/src/providers/CoreStateProvider.tsx
  • app/src/providers/__tests__/CoreStateProvider.test.tsx
  • app/src/services/api/rewardsApi.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src/services/api/rewardsApi.ts

📝 Walkthrough

Walkthrough

Rate-limit bootstrap/poll warnings to specific retry checkpoints and add a 15s timeout plus structured error mapping for the rewards API; tests updated to assert warning checkpoints and timeout behavior.

Changes

Bootstrap Warning Rate-Limiting

Layer / File(s) Summary
Warning decision predicate
app/src/providers/CoreStateProvider.tsx
Adds exported shouldWarnForBootstrapFailure(failureCount) to decide which failure attempts should warn.
Polling integration
app/src/providers/CoreStateProvider.tsx
Refresh loop now conditionally calls console.warn only when the predicate returns true instead of warning on every failure.
Tests for warning checkpoints
app/src/providers/__tests__/CoreStateProvider.test.tsx
Imports shouldWarnForBootstrapFailure and adds a test that filters attempts and expects warnings at specific checkpoint attempts (test asserts [1, 5, 10, 15]).

Rewards API Timeout Handling

Layer / File(s) Summary
Timeout constants
app/src/services/api/rewardsApi.ts
Adds REWARDS_REQUEST_TIMEOUT_MS (15s) and REWARDS_TIMEOUT_MESSAGE.
Error normalization and timeout detection
app/src/services/api/rewardsApi.ts
Adds errorText(error) and isTimeoutError(error) helpers to extract readable messages and classify timeouts.
Request implementation and success logging
app/src/services/api/rewardsApi.ts
getMyRewards() passes { timeout: REWARDS_REQUEST_TIMEOUT_MS } to apiClient.get(), wraps the call in try/catch, maps timeouts to REWARDS_TIMEOUT_MESSAGE, throws { success: false, error } on failure, and logs achievementCount on success.
Rewards API tests
app/src/services/api/__tests__/rewardsApi.test.ts
Clears mocks beforeEach, verifies timeout option is passed to apiClient.get, and adds a test that a timed-out /rewards/me rejects with the actionable timeout message.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I nibbled through the noisy logs tonight,
Picked warning hops to only a few bright lights,
A fifteen-second leash keeps rewards from stray,
Now console gardens breathe easy all day,
Hooray for quieter hops and kinder delays!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% 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 accurately summarizes the main changes: fixing console spam from core-state polling logs and adding rewards API timeout handling with improved UX.
Linked Issues check ✅ Passed All primary objectives from issue #1235 are met: rate-limited poll warnings at checkpoints [1,5,10,15], 15s timeout with user-friendly message, graceful failure handling, regression test coverage, and diff-coverage compliance.
Out of Scope Changes check ✅ Passed All changes directly address issue #1235: core-state poll warning rate-limiting, rewards timeout mapping, test coverage, and logger namespacing. No unrelated modifications detected.

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

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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.

@jwalin-shah jwalin-shah marked this pull request as ready for review May 7, 2026 06:24
@jwalin-shah jwalin-shah requested a review from a team May 7, 2026 06:24
@oxoxDev
Copy link
Copy Markdown
Contributor

oxoxDev commented May 7, 2026

Tested locally on a merge of upstream/main into this branch (cold-boot needs #1324's redux-persist localStorage adapter; without it the dev host throws storage.getItem is not a function and renders white screen).

Verdict: works as intended

AC Status Evidence
AC1 — no repeated [core-state] poll failed PASS Console shows warns at attempts 1, 5, 10, 15 only — throttle confirmed via shouldWarnForBootstrapFailure
AC2 — regression safety PASS rewardsApi.test.ts 5/5 + CoreStateProvider.test.tsx 8/8 green
AC4 — graceful failure, no retry storm PASS After 15s timeout, Rewards page flips to a yellow warning banner with the new copy + manual "Try again" button. No auto-retry. Page degrades to Discord connect cards + "Rewards sync pending" empty state
AC5 — useful reason/status, no noisy repetition PASS Throttled warns carry sanitized error {message: "Failed to fetch"}; [rewards] backend snapshot unavailable debug fires once per attempt

Repro for the timeout path: monkey-patched window.fetch to honor AbortSignal and hang forever on /rewards/me — at 15s the AbortController fires, isTimeoutError matches AbortError, friendly copy reaches the UI.

Action item before merge

Branch needs upstream/main merged in (or rebased) so #1324 lands here — otherwise the dev-host cold-boot crash blocks anyone who tries to test or run CI. Without that, the white-screen blocks even reaching the Rewards page.

(Tried to push the merge commit on your behalf via maintainer-edits but lacked write access to your fork — leaving it to you.)

Copy link
Copy Markdown
Member

@senamakel senamakel left a comment

Choose a reason for hiding this comment

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

pls see above

@senamakel
Copy link
Copy Markdown
Member

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 9, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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 (3)
app/src/providers/__tests__/CoreStateProvider.test.tsx (1)

222-228: ⚡ Quick win

Consider extending test to cover attempt 15 for completeness.

The test correctly validates checkpoints [1, 5, 10] within a 12-attempt sequence. Per the PR comment from oxoxDev, warnings also fire at attempt 15. Extending the test range to { length: 16 } and expecting [1, 5, 10, 15] would provide stronger validation of the "every 5" recurring pattern.

📋 Proposed extension
-  it('rate-limits repeated bootstrap poll warnings to useful checkpoints', () => {
-    const warnedAttempts = Array.from({ length: 12 }, (_, index) => index + 1).filter(
-      shouldWarnForBootstrapFailure
-    );
-
-    expect(warnedAttempts).toEqual([1, 5, 10]);
-  });
+  it('rate-limits repeated bootstrap poll warnings to useful checkpoints', () => {
+    const warnedAttempts = Array.from({ length: 16 }, (_, index) => index + 1).filter(
+      shouldWarnForBootstrapFailure
+    );
+
+    expect(warnedAttempts).toEqual([1, 5, 10, 15]);
+  });
🤖 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 `@app/src/providers/__tests__/CoreStateProvider.test.tsx` around lines 222 -
228, Update the test that computes warnedAttempts to include attempt 15: change
the Array.from length from 12 to 16 in the test inside
CoreStateProvider.test.tsx and update the expected array in the expect(...)
assertion to [1, 5, 10, 15]; this ensures the shouldWarnForBootstrapFailure
logic (the function under test) is validated for the recurring "every 5"
checkpoint including attempt 15.
app/src/services/api/rewardsApi.ts (2)

41-46: ⚖️ Poor tradeoff

Consider centralizing error extraction utilities.

The errorText helper is similar to sanitizeError in CoreStateProvider.tsx (lines 57-70). While both serve slightly different purposes, extracting error messages is a common pattern that could benefit from a shared utility in app/src/utils/. This is not blocking, but worth considering for future refactoring to reduce duplication.

🤖 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 `@app/src/services/api/rewardsApi.ts` around lines 41 - 46, The helper function
errorText duplicates logic already in sanitizeError; extract the common
error-extraction logic into a new shared utility (e.g., create a function like
extractErrorMessage in app/src/utils/) and replace the local errorText and
usages of sanitizeError to call that utility; update the exported names (keep
errorText and sanitizeError wrappers if needed) so callers (including the
function errorText in rewardsApi.ts and sanitizeError in CoreStateProvider.tsx)
import the single implementation to avoid duplication.

131-148: ⚡ Quick win

Timeout implementation is correct; consider using namespaced debug logger.

The timeout and error handling logic correctly implements the PR objectives: 15s timeout, user-friendly timeout message, and backend error preservation. However, per coding guidelines for app/src/**/*.ts, frontend logging should use namespaced debug (from the debug package) rather than console.debug. Other files in this PR (e.g., CoreStateProvider.tsx line 43) follow this pattern with debugFactory('core-state').

♻️ Proposed refactor to use namespaced debug

Add the import at the top:

+import debugFactory from 'debug';
+
 import type { ApiResponse } from '../../types/api';
 import type { RewardsAchievement, RewardsSnapshot } from '../../types/rewards';
 import { apiClient } from '../apiClient';
 
+const log = debugFactory('rewards-api');
+
 const REWARDS_REQUEST_TIMEOUT_MS = 15_000;

Then update the debug calls:

     } catch (error) {
       const message = isTimeoutError(error)
         ? REWARDS_TIMEOUT_MESSAGE
         : errorText(error) || 'Unable to load rewards';
-      console.debug('[rewards] backend snapshot unavailable', { message });
+      log('backend snapshot unavailable message=%s', message);
       throw { success: false, error: message };
     }
 
-    console.debug('[rewards] loaded backend snapshot', {
-      achievementCount: Array.isArray((response.data as { achievements?: unknown[] })?.achievements)
-        ? (response.data as { achievements: unknown[] }).achievements.length
-        : 0,
-    });
+    const achievementCount = Array.isArray((response.data as { achievements?: unknown[] })?.achievements)
+      ? (response.data as { achievements: unknown[] }).achievements.length
+      : 0;
+    log('loaded backend snapshot achievementCount=%d', achievementCount);
     return normalizeRewardsSnapshot(response.data);

As per coding guidelines: "Use namespaced debug for frontend logging with dev-only detail; avoid logging secrets or full PII."

🤖 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 `@app/src/services/api/rewardsApi.ts` around lines 131 - 148, Replace the
console.debug call with the project’s namespaced debug logger: add an import for
the debug factory and create a module-scoped logger (e.g., const debug =
debugFactory('rewards-api')) at the top of this module, then change the
console.debug('[rewards] backend snapshot unavailable', { message }) call inside
the catch to use debug (e.g., debug('backend snapshot unavailable %O', { message
})) so logging follows the frontend namespaced debug pattern used elsewhere like
CoreStateProvider.
🤖 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 `@app/src/providers/__tests__/CoreStateProvider.test.tsx`:
- Around line 222-228: Update the test that computes warnedAttempts to include
attempt 15: change the Array.from length from 12 to 16 in the test inside
CoreStateProvider.test.tsx and update the expected array in the expect(...)
assertion to [1, 5, 10, 15]; this ensures the shouldWarnForBootstrapFailure
logic (the function under test) is validated for the recurring "every 5"
checkpoint including attempt 15.

In `@app/src/services/api/rewardsApi.ts`:
- Around line 41-46: The helper function errorText duplicates logic already in
sanitizeError; extract the common error-extraction logic into a new shared
utility (e.g., create a function like extractErrorMessage in app/src/utils/) and
replace the local errorText and usages of sanitizeError to call that utility;
update the exported names (keep errorText and sanitizeError wrappers if needed)
so callers (including the function errorText in rewardsApi.ts and sanitizeError
in CoreStateProvider.tsx) import the single implementation to avoid duplication.
- Around line 131-148: Replace the console.debug call with the project’s
namespaced debug logger: add an import for the debug factory and create a
module-scoped logger (e.g., const debug = debugFactory('rewards-api')) at the
top of this module, then change the console.debug('[rewards] backend snapshot
unavailable', { message }) call inside the catch to use debug (e.g.,
debug('backend snapshot unavailable %O', { message })) so logging follows the
frontend namespaced debug pattern used elsewhere like CoreStateProvider.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 153f0bef-eceb-4b84-8d13-a6daa2c8f188

📥 Commits

Reviewing files that changed from the base of the PR and between 09f8f12 and f113824.

📒 Files selected for processing (4)
  • app/src/providers/CoreStateProvider.tsx
  • app/src/providers/__tests__/CoreStateProvider.test.tsx
  • app/src/services/api/__tests__/rewardsApi.test.ts
  • app/src/services/api/rewardsApi.ts

coderabbitai[bot]
coderabbitai Bot previously approved these changes May 9, 2026
senamakel added 2 commits May 13, 2026 20:10
… and extended checkpoint test

- Replace console.debug calls in rewardsApi.ts with debugFactory('rewards-api')
  logger per frontend logging guidelines (addresses @coderabbitai on rewardsApi.ts:131-148)
- Extend shouldWarnForBootstrapFailure test from length 12 to 16, expect [1,5,10,15]
  to validate the recurring every-5 pattern including attempt 15
  (addresses @coderabbitai on CoreStateProvider.test.tsx:222-228)
- Drop misleading /${MAX_BOOTSTRAP_RETRIES} denominator from rate-limited warn message
  (addresses @senamakel review)
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.

Core state polling and rewards API request time out in the app console

4 participants