Skip to content

fix: persist GitHub star sync immediately and guard against empty-backend overwrite#142

Merged
AmintaCCCP merged 2 commits into
AmintaCCCP:mainfrom
iamvicliu:fix/github-sync-data-persistence
May 13, 2026
Merged

fix: persist GitHub star sync immediately and guard against empty-backend overwrite#142
AmintaCCCP merged 2 commits into
AmintaCCCP:mainfrom
iamvicliu:fix/github-sync-data-persistence

Conversation

@iamvicliu
Copy link
Copy Markdown
Contributor

@iamvicliu iamvicliu commented May 13, 2026

Summary

Two related data-loss bugs when the backend sync feature is enabled. Both were found during a self-hosted deployment and reproduce reliably.

Bug 1 — Starred repos lost if page is refreshed within 2 seconds of sync (Header.tsx)

After fetching starred repos from GitHub, the new repository list is written to the Zustand store but only flushed to the backend after a 2-second debounce. If the user refreshes the page within that window:

  1. The debounce timer is destroyed.
  2. PUT /api/repositories never fires.
  3. On the next load, syncFromBackend fetches the still-empty backend and overwrites the local store.
  4. All just-synced repos disappear.

Fix: call forceSyncToBackend() immediately after setRepositories() in Header.tsx, so the data reaches the backend before the user can navigate away.

Bug 2 — Empty backend silently wipes locally-cached repos (autoSync.ts)

On every page load, syncFromBackend fetches repos from the backend and unconditionally overwrites the Zustand store — even when the backend returns an empty array. This triggers in several scenarios:

  • First-time backend configuration (backend has no data yet)
  • Backend was reset or migrated
  • Bug 1 race above (backend was never populated)

Any repos cached in localStorage from a previous session are silently cleared.

Fix: skip setRepositories when the backend returns zero repositories but the local store is non-empty, so locally-cached data is never replaced by an empty backend response.

Reproduction

  1. Enable backend sync (set API_SECRET).
  2. Click Sync to fetch starred repos.
  3. Refresh the page within ~2 seconds. → All repos are gone (Bug 1).

Or:

  1. Configure a fresh backend (no data).
  2. In a previous browser session with repos in localStorage, open the app. → Repos disappear immediately after syncFromBackend runs (Bug 2).

Test plan

  • Sync starred repos → refresh immediately → repos still present after reload
  • Open app with a fresh/empty backend but repos in localStorage → repos are not wiped on load
  • Normal flow (backend already has repos) → repos still load correctly from backend on refresh
  • Unstar a repo → refresh → repo correctly absent (deletion still propagates to backend)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Prevented local repository data from being overwritten by an empty backend bootstrap; local cache is preserved and a push is queued instead.
    • Trigger immediate backend sync after local repository updates (fire-and-forget) to ensure changes are sent promptly; errors are safely logged.

Review Change Stack

…kend overwrite

Two related data-loss bugs when using the backend sync feature:

1. src/components/Header.tsx
   After fetching starred repos from GitHub, the store update was only
   flushed to the backend after a 2-second debounce. If the user refreshed
   the page within that window the debounce timer was destroyed, the PUT
   never fired, and the data was lost on the next load (when syncFromBackend
   overwrites the local store with the still-empty backend).
   Fix: call forceSyncToBackend() immediately after setRepositories() so
   the data reaches the backend before the user can navigate away.

2. src/services/autoSync.ts (syncFromBackend)
   On every page load, syncFromBackend fetches from the backend and
   overwrites the Zustand store. If the backend has zero repositories
   (e.g. first-time setup, backend was reset, or the debounce race above
   occurred), it silently clears whatever was cached in localStorage.
   Fix: skip the setRepositories call when the backend returns an empty
   list but the local store already has repositories, so locally-cached
   data is never replaced with an empty response.

Together these two changes make starred-repo data survive a page refresh
even in scenarios where the backend has not yet received a successful sync.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 13, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4d6a12c3-e189-4de6-b1a7-d8a7d2b9bb03

📥 Commits

Reviewing files that changed from the base of the PR and between 0dbcea0 and 9d139d6.

📒 Files selected for processing (2)
  • src/components/Header.tsx
  • src/services/autoSync.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/components/Header.tsx
  • src/services/autoSync.ts

📝 Walkthrough

Walkthrough

autoSync now preserves local repositories when the backend appears as an initial empty bootstrap; Header.handleSync now calls forceSyncToBackend immediately after updating the local repositories (fire-and-forget, errors logged).

Changes

Backend Sync Guard and Header Integration

Layer / File(s) Summary
Repository sync guard logic
src/services/autoSync.ts
syncFromBackend now detects an empty backend bootstrap and, if local repos exist and the repos fingerprint is still the initial value, preserves local repositories and sets _hasPendingPush = true instead of overwriting the store or updating _lastHash.repos.
Header force-sync integration
src/components/Header.tsx
Header imports forceSyncToBackend and invokes it (fire-and-forget with .catch(console.error)) immediately after merging fetched repositories and calling setRepositories(mergedRepositories).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 I hop through code with ears up high,
I guard the cache from empty sky.
When headers sync I give a nudge,
A backend push — a tiny judge.
Repos stay safe, the syncs won't sigh.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and specifically summarizes the two main fixes: immediate persistence of GitHub star sync and protection against empty-backend data loss.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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: 1

🤖 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 `@src/services/autoSync.ts`:
- Around line 136-139: The current guard prevents applying an empty backend repo
list when localRepos is non-empty, blocking legitimate “delete all repos” from
another device; update the condition around state.setRepositories so an empty
backend is accepted when it represents a new authoritative backend state.
Specifically, in the block that references backendRepos, localRepos,
state.setRepositories, _lastHash.repos and hashes.repos, change the if to also
allow assignment when hashes.repos has changed since _lastHash.repos (e.g.,
backendRepos.length > 0 || localRepos.length === 0 || hashes.repos !==
_lastHash.repos) so an authoritative empty backend will overwrite local repos
and convergence can occur.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: e15ff28c-da89-4c7f-a1b0-2978e8de71b3

📥 Commits

Reviewing files that changed from the base of the PR and between daae883 and 0dbcea0.

📒 Files selected for processing (2)
  • src/components/Header.tsx
  • src/services/autoSync.ts

Comment thread src/services/autoSync.ts Outdated
- Differentiate bootstrap-empty from authoritative-empty backend:
  only preserve local cache on first-ever sync (_lastHash.repos === ''),
  accept empty backend on subsequent syncs so cross-device deletes converge.
  (addresses CodeRabbit review)
- Replace void forceSyncToBackend() with .catch(console.error) so sync
  failures are logged instead of silently swallowed.
@AmintaCCCP AmintaCCCP merged commit c292c11 into AmintaCCCP:main May 13, 2026
5 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.

Token 每小时5000限制 如何避免没额度还能保存已经获取的,,?

2 participants