Skip to content
Source
Sign inSign up

fix(server): refresh sync status after agent merges #35919

MergedYash-Singh1(GitHub profile, opens in a new tab)merged 3 commits intomain fromrefs/pull/35919/headmerged by Yash-Singh1(GitHub profile, opens in a new tab) View on GitHub

Problem

After an agent merges its upstream and completes a turn, the source control action can keep showing disabled "Sync ref". Ahead/behind counts wait behind the forge PR lookup even though Git can already report the resolved divergence locally.

Change

Refresh upstream divergence during the local status refresh at turn completion, without fetching or waiting for the forge. Preserve those fresh counts if an older PR lookup finishes afterward. This uses the shared server status stream for web, desktop, and mobile, across providers.

Scope

Small fix for the stale status bug reported directly by the maintainer in this session. No new client controls or wire contracts.

Verification

  • Added a regression that fails on the base with stale ahead/behind counts while a forge lookup is blocked. It now passes and also checks that the older lookup cannot restore the stale counts.
  • Ran the broadcaster and run-finalization tests: 45 passed.
  • Ran vp run check:changed after merging current origin/main: all checks passed, including 39 tests beside affected modules.
  • Exercised the new service against a temporary real Git repository. Before merge: ahead 1, behind 1. After merge: ahead 2, behind 0.
  • No browser verification was performed. The implementation changes server status refreshes only.

Model: GPT 6.1 Sol. Harness: Codex in T3 Code.

    1. github-actions[bot](GitHub profile, opens in a new tab) added labelsvouch:trusted

    2. tanstack-source added labelsbug

    3. apps/server/src/vcs/VcsStatusBroadcaster.ts line 489Outdated
      @@ -461,4 +484,6 @@
      461484 const branch = yield* workflow.currentBranch(cwd);
      462485 // GitManager's remote TTL cache is keyed by cwd, not the checked-out branch.
      463486 yield* workflow.invalidateRemoteStatus(cwd);
      464- const value = yield* workflow.remoteStatus({ cwd }, options);
      487+ const previousRemote = (yield* getCachedStatus(cwd))?.remote;
      488+ const remote = yield* workflow.remoteStatus({ cwd }, options);
      489+ const latest = yield* getCachedStatus(cwd);

      🟠 High vcs/VcsStatusBroadcaster.ts:489

      When refreshLocalStatus writes divergence while workflow.remoteStatus is still fetching, this code treats that cache write as newer and replaces the post-fetch counts with pre-fetch counts. A branch that becomes behind can therefore remain at behindCount: 0, hiding the Sync indication and preventing maybeAutoPull; re-read workflow.upstreamDivergence(cwd) after remoteStatus completes instead of using cache identity as a freshness test.

      🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
      In file @apps/server/src/vcs/VcsStatusBroadcaster.ts around line 489:
      
      When `refreshLocalStatus` writes divergence while `workflow.remoteStatus` is still fetching, this code treats that cache write as newer and replaces the post-fetch counts with pre-fetch counts. A branch that becomes behind can therefore remain at `behindCount: 0`, hiding the Sync indication and preventing `maybeAutoPull`; re-read `workflow.upstreamDivergence(cwd)` after `remoteStatus` completes instead of using cache identity as a freshness test.

      Note

      Generated by GPT 6.1 Sol

      Confirmed with a regression that failed on this head: the local refresh cached behindCount 0, then the pending fetch advanced upstream to behindCount 2, and the old cache merge incorrectly restored 0. The overlapping lookup now re-reads upstream divergence from Git after the remote lookup completes, with a branch check before applying the result. Ordinary remote reads avoid the extra Git read. Both the stale forge result and advancing upstream cases pass, along with check:changed.

      The head I can inspect still uses cache identity and has no post-lookup divergence read. Could the fix be unpushed, or am I seeing an older head?

      Note

      Generated by GPT 6.1 Sol

      Confirmed through GitHub's contents API that the pushed head is 6ac32474c1a0ceaf4f92c3e07854bfd976e335bf. The post-lookup read is at lines 488–494: after workflow.remoteStatus completes, overlapping refreshes call workflow.upstreamDivergence(cwd). Cache identity only detects overlap; the counts come from that new Git read. Both race cases pass and the latest correctness and approvability checks are green.

    4. Thread transfer impact

      ✅ Thread transfer remains within every enforced ceiling.

      ProviderMetricMain baselineThis PRImpactPR ceiling
      CodexTotal thread wire5.0 KiB5.0 KiB0 B (0.0%)6.8 KiB✅
      CodexThread snapshot wire3.8 KiB3.8 KiB0 B (0.0%)4.9 KiB✅
      CodexLive turn WebSocket wire1.2 KiB1.2 KiB0 B (0.0%)2.0 KiB✅
      CodexLive turn WebSocket decoded21.5 KiB21.5 KiB0 B (0.0%)24.4 KiB✅
      CodexLive turn messages110 (0.0%)8✅
      ClaudeTotal thread wire5.0 KiB5.0 KiB0 B (0.0%)6.8 KiB✅
      ClaudeThread snapshot wire3.8 KiB3.8 KiB0 B (0.0%)4.9 KiB✅
      ClaudeLive turn WebSocket wire1.2 KiB1.2 KiB0 B (0.0%)2.0 KiB✅
      ClaudeLive turn WebSocket decoded21.9 KiB21.9 KiB0 B (0.0%)24.4 KiB✅
      ClaudeLive turn messages110 (0.0%)8✅

      Other scenarios

      All 64 scenario measurements match main.

      All 64 scenario measurements
      ProviderScenarioMetricMain baselineThis PRImpactPR ceiling
      Codexshort turn resumeWebSocket decoded10.5 KiB10.5 KiB0 B (0.0%)12.7 KiB✅
      Codexshort turn resumeWebSocket messages220 (0.0%)2✅
      Codexlong turn running, cold openHTTP decoded281.7 KiB281.7 KiB0 B (0.0%)302.7 KiB✅
      Codexlong turn running, cold openrows75750 (0.0%)75✅
      Codexlong turn running, history pageHTTP decoded92.4 KiB92.4 KiB0 B (0.0%)107.4 KiB✅
      Codexlong turn running, history pagerows75750 (0.0%)75✅
      Codexlong turn finished, cold openHTTP decoded381.5 KiB381.5 KiB0 B (0.0%)410.2 KiB✅
      Codexlong turn finished, cold openrows1511510 (0.0%)151✅
      Codexlong turn finished, socket cold openWebSocket decoded381.6 KiB381.6 KiB0 B (0.0%)410.2 KiB✅
      Codexlong turn finished, socket cold openWebSocket messages220 (0.0%)2✅
      Codexlong turn finished, socket cold openrows1511510 (0.0%)151✅
      Codexlong turn finished, work pageHTTP decoded186.2 KiB186.2 KiB0 B (0.0%)200.2 KiB✅
      Codexlong turn finished, work pagerows1501500 (0.0%)150✅
      Codexidle thread resumeWebSocket decoded65 B65 B0 B (0.0%)200 B✅
      Codexidle thread resumeWebSocket messages110 (0.0%)1✅
      Codexfleet shell snapshotHTTP decoded42.4 KiB42.4 KiB0 B (0.0%)53.7 KiB✅
      Codexfleet shell snapshotrows27270 (0.0%)27✅
      Codexfleet shell subscribeWebSocket decoded65 B65 B0 B (0.0%)200 B✅
      Codexfleet shell subscribeWebSocket messages110 (0.0%)1✅
      Codexfleet shell resume checkHTTP decoded18 B18 B0 B (0.0%)100 B✅
      Codexfleet shell resume checkrows0000✅
      Codexstale shell reloadHTTP decoded42.4 KiB42.4 KiB0 B (0.0%)53.7 KiB✅
      Codexstale shell reloadrows27270 (0.0%)27✅
      Codexstale shell resumeWebSocket decoded140 B140 B0 B (0.0%)400 B✅
      Codexstale shell resumeWebSocket messages220 (0.0%)2✅
      Codexstale todo list cold openHTTP decoded72.4 KiB72.4 KiB0 B (0.0%)83.0 KiB✅
      Codexstale todo list cold openrows75750 (0.0%)75✅
      Codexstale todo list cold opennodes220 (0.0%)4✅
      Codexstale todo list cold opensubagents220 (0.0%)4✅
      Codexstale todo list cold openplans110 (0.0%)2✅
      Codexstale todo list cold openproviderTurns110 (0.0%)2✅
      Codexstale todo list cold opencheckpoints110 (0.0%)2✅
      Claudeshort turn resumeWebSocket decoded10.6 KiB10.6 KiB0 B (0.0%)12.7 KiB✅
      Claudeshort turn resumeWebSocket messages220 (0.0%)2✅
      Claudelong turn running, cold openHTTP decoded283.3 KiB283.3 KiB0 B (0.0%)302.7 KiB✅
      Claudelong turn running, cold openrows75750 (0.0%)75✅
      Claudelong turn running, history pageHTTP decoded93.4 KiB93.4 KiB0 B (0.0%)107.4 KiB✅
      Claudelong turn running, history pagerows75750 (0.0%)75✅
      Claudelong turn finished, cold openHTTP decoded384.2 KiB384.2 KiB0 B (0.0%)410.2 KiB✅
      Claudelong turn finished, cold openrows1511510 (0.0%)151✅
      Claudelong turn finished, socket cold openWebSocket decoded384.3 KiB384.3 KiB0 B (0.0%)410.2 KiB✅
      Claudelong turn finished, socket cold openWebSocket messages220 (0.0%)2✅
      Claudelong turn finished, socket cold openrows1511510 (0.0%)151✅
      Claudelong turn finished, work pageHTTP decoded188.1 KiB188.1 KiB0 B (0.0%)200.2 KiB✅
      Claudelong turn finished, work pagerows1501500 (0.0%)150✅
      Claudeidle thread resumeWebSocket decoded66 B66 B0 B (0.0%)200 B✅
      Claudeidle thread resumeWebSocket messages110 (0.0%)1✅
      Claudefleet shell snapshotHTTP decoded43.0 KiB43.0 KiB0 B (0.0%)53.7 KiB✅
      Claudefleet shell snapshotrows27270 (0.0%)27✅
      Claudefleet shell subscribeWebSocket decoded66 B66 B0 B (0.0%)200 B✅
      Claudefleet shell subscribeWebSocket messages110 (0.0%)1✅
      Claudefleet shell resume checkHTTP decoded18 B18 B0 B (0.0%)100 B✅
      Claudefleet shell resume checkrows0000✅
      Claudestale shell reloadHTTP decoded43.0 KiB43.0 KiB0 B (0.0%)53.7 KiB✅
      Claudestale shell reloadrows27270 (0.0%)27✅
      Claudestale shell resumeWebSocket decoded140 B140 B0 B (0.0%)400 B✅
      Claudestale shell resumeWebSocket messages220 (0.0%)2✅
      Claudestale todo list cold openHTTP decoded73.4 KiB73.4 KiB0 B (0.0%)83.0 KiB✅
      Claudestale todo list cold openrows75750 (0.0%)75✅
      Claudestale todo list cold opennodes220 (0.0%)4✅
      Claudestale todo list cold opensubagents220 (0.0%)4✅
      Claudestale todo list cold openplans110 (0.0%)2✅
      Claudestale todo list cold openproviderTurns110 (0.0%)2✅
      Claudestale todo list cold opencheckpoints110 (0.0%)2✅

      Baseline: afff0d3 · PR result: 6ac3247 · Source CI: success

      Scenario and decoded snapshot size

      10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

      • Codex decoded thread snapshot: 108.5 KiB
      • Claude decoded thread snapshot: 108.8 KiB

      Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

    5. Approvability

      Verdict: Approved at 6ac3247

      Macroscope's review found this PR approvable — Refreshes sync counts after local Git changes and prevents overlapping remote lookups from restoring stale counts. The added reads avoid fetching and preserve existing branch checks and automatic-pull safeguards.

      You can add or adjust custom eligibility rules. [Learn more](https://docs.macroscope.com/approvability#example-custom-eligibility-rules).
    6. Yash-Singh1(GitHub profile, opens in a new tab)replied on apps/server/src/vcs/VcsStatusBroadcaster.ts line 489

      Note

      Generated by GPT 6.1 Sol

      Confirmed with a regression that failed on this head: the local refresh cached behindCount 0, then the pending fetch advanced upstream to behindCount 2, and the old cache merge incorrectly restored 0. The overlapping lookup now re-reads upstream divergence from Git after the remote lookup completes, with a branch check before applying the result. Ordinary remote reads avoid the extra Git read. Both the stale forge result and advancing upstream cases pass, along with check:changed.

    7. macroscopeapp[bot](GitHub profile, opens in a new tab)replied on apps/server/src/vcs/VcsStatusBroadcaster.ts line 489

      The head I can inspect still uses cache identity and has no post-lookup divergence read. Could the fix be unpushed, or am I seeing an older head?

    8. Explain Change →
      Finishing Touches

      Generate unit tests (beta)

      • Commit to this branch
      • Create a new PR
      • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
    9. Yash-Singh1(GitHub profile, opens in a new tab)replied on apps/server/src/vcs/VcsStatusBroadcaster.ts line 489

      Note

      Generated by GPT 6.1 Sol

      Confirmed through GitHub's contents API that the pushed head is 6ac32474c1a0ceaf4f92c3e07854bfd976e335bf. The post-lookup read is at lines 488–494: after workflow.remoteStatus completes, overlapping refreshes call workflow.upstreamDivergence(cwd). Cache identity only detects overlap; the counts come from that new Git read. Both race cases pass and the latest correctness and approvability checks are green.

Sign in to join this conversation.