fix(server): refresh sync status after agent merges #35919
main fromrefs/pull/35919/head·merged by Yash-Singh1(GitHub profile, opens in a new tab) View on GitHubProblem#
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:changedafter merging currentorigin/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
tanstack-source added labelsbug
github-actions[bot](GitHub profile, opens in a new tab) added labelssize:M
- 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:489When
refreshLocalStatuswrites divergence whileworkflow.remoteStatusis 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 atbehindCount: 0, hiding the Sync indication and preventingmaybeAutoPull; re-readworkflow.upstreamDivergence(cwd)afterremoteStatuscompletes 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.
Thread transfer impact#
✅ Thread transfer remains within every enforced ceiling.
Provider Metric Main baseline This PR Impact PR ceiling Codex Total thread wire 5.0 KiB 5.0 KiB 0 B (0.0%) 6.8 KiB ✅ Codex Thread snapshot wire 3.8 KiB 3.8 KiB 0 B (0.0%) 4.9 KiB ✅ Codex Live turn WebSocket wire 1.2 KiB 1.2 KiB 0 B (0.0%) 2.0 KiB ✅ Codex Live turn WebSocket decoded 21.5 KiB 21.5 KiB 0 B (0.0%) 24.4 KiB ✅ Codex Live turn messages 1 1 0 (0.0%) 8 ✅ Claude Total thread wire 5.0 KiB 5.0 KiB 0 B (0.0%) 6.8 KiB ✅ Claude Thread snapshot wire 3.8 KiB 3.8 KiB 0 B (0.0%) 4.9 KiB ✅ Claude Live turn WebSocket wire 1.2 KiB 1.2 KiB 0 B (0.0%) 2.0 KiB ✅ Claude Live turn WebSocket decoded 21.9 KiB 21.9 KiB 0 B (0.0%) 24.4 KiB ✅ Claude Live turn messages 1 1 0 (0.0%) 8 ✅ Other scenarios#
All 64 scenario measurements match
main.All 64 scenario measurements
Provider Scenario Metric Main baseline This PR Impact PR ceiling Codex short turn resumeWebSocket decoded10.5 KiB 10.5 KiB 0 B (0.0%) 12.7 KiB ✅ Codex short turn resumeWebSocket messages2 2 0 (0.0%) 2 ✅ Codex long turn running, cold openHTTP decoded281.7 KiB 281.7 KiB 0 B (0.0%) 302.7 KiB ✅ Codex long turn running, cold openrows75 75 0 (0.0%) 75 ✅ Codex long turn running, history pageHTTP decoded92.4 KiB 92.4 KiB 0 B (0.0%) 107.4 KiB ✅ Codex long turn running, history pagerows75 75 0 (0.0%) 75 ✅ Codex long turn finished, cold openHTTP decoded381.5 KiB 381.5 KiB 0 B (0.0%) 410.2 KiB ✅ Codex long turn finished, cold openrows151 151 0 (0.0%) 151 ✅ Codex long turn finished, socket cold openWebSocket decoded381.6 KiB 381.6 KiB 0 B (0.0%) 410.2 KiB ✅ Codex long turn finished, socket cold openWebSocket messages2 2 0 (0.0%) 2 ✅ Codex long turn finished, socket cold openrows151 151 0 (0.0%) 151 ✅ Codex long turn finished, work pageHTTP decoded186.2 KiB 186.2 KiB 0 B (0.0%) 200.2 KiB ✅ Codex long turn finished, work pagerows150 150 0 (0.0%) 150 ✅ Codex idle thread resumeWebSocket decoded65 B 65 B 0 B (0.0%) 200 B ✅ Codex idle thread resumeWebSocket messages1 1 0 (0.0%) 1 ✅ Codex fleet shell snapshotHTTP decoded42.4 KiB 42.4 KiB 0 B (0.0%) 53.7 KiB ✅ Codex fleet shell snapshotrows27 27 0 (0.0%) 27 ✅ Codex fleet shell subscribeWebSocket decoded65 B 65 B 0 B (0.0%) 200 B ✅ Codex fleet shell subscribeWebSocket messages1 1 0 (0.0%) 1 ✅ Codex fleet shell resume checkHTTP decoded18 B 18 B 0 B (0.0%) 100 B ✅ Codex fleet shell resume checkrows0 0 0 0 ✅ Codex stale shell reloadHTTP decoded42.4 KiB 42.4 KiB 0 B (0.0%) 53.7 KiB ✅ Codex stale shell reloadrows27 27 0 (0.0%) 27 ✅ Codex stale shell resumeWebSocket decoded140 B 140 B 0 B (0.0%) 400 B ✅ Codex stale shell resumeWebSocket messages2 2 0 (0.0%) 2 ✅ Codex stale todo list cold openHTTP decoded72.4 KiB 72.4 KiB 0 B (0.0%) 83.0 KiB ✅ Codex stale todo list cold openrows75 75 0 (0.0%) 75 ✅ Codex stale todo list cold opennodes2 2 0 (0.0%) 4 ✅ Codex stale todo list cold opensubagents2 2 0 (0.0%) 4 ✅ Codex stale todo list cold openplans1 1 0 (0.0%) 2 ✅ Codex stale todo list cold openproviderTurns1 1 0 (0.0%) 2 ✅ Codex stale todo list cold opencheckpoints1 1 0 (0.0%) 2 ✅ Claude short turn resumeWebSocket decoded10.6 KiB 10.6 KiB 0 B (0.0%) 12.7 KiB ✅ Claude short turn resumeWebSocket messages2 2 0 (0.0%) 2 ✅ Claude long turn running, cold openHTTP decoded283.3 KiB 283.3 KiB 0 B (0.0%) 302.7 KiB ✅ Claude long turn running, cold openrows75 75 0 (0.0%) 75 ✅ Claude long turn running, history pageHTTP decoded93.4 KiB 93.4 KiB 0 B (0.0%) 107.4 KiB ✅ Claude long turn running, history pagerows75 75 0 (0.0%) 75 ✅ Claude long turn finished, cold openHTTP decoded384.2 KiB 384.2 KiB 0 B (0.0%) 410.2 KiB ✅ Claude long turn finished, cold openrows151 151 0 (0.0%) 151 ✅ Claude long turn finished, socket cold openWebSocket decoded384.3 KiB 384.3 KiB 0 B (0.0%) 410.2 KiB ✅ Claude long turn finished, socket cold openWebSocket messages2 2 0 (0.0%) 2 ✅ Claude long turn finished, socket cold openrows151 151 0 (0.0%) 151 ✅ Claude long turn finished, work pageHTTP decoded188.1 KiB 188.1 KiB 0 B (0.0%) 200.2 KiB ✅ Claude long turn finished, work pagerows150 150 0 (0.0%) 150 ✅ Claude idle thread resumeWebSocket decoded66 B 66 B 0 B (0.0%) 200 B ✅ Claude idle thread resumeWebSocket messages1 1 0 (0.0%) 1 ✅ Claude fleet shell snapshotHTTP decoded43.0 KiB 43.0 KiB 0 B (0.0%) 53.7 KiB ✅ Claude fleet shell snapshotrows27 27 0 (0.0%) 27 ✅ Claude fleet shell subscribeWebSocket decoded66 B 66 B 0 B (0.0%) 200 B ✅ Claude fleet shell subscribeWebSocket messages1 1 0 (0.0%) 1 ✅ Claude fleet shell resume checkHTTP decoded18 B 18 B 0 B (0.0%) 100 B ✅ Claude fleet shell resume checkrows0 0 0 0 ✅ Claude stale shell reloadHTTP decoded43.0 KiB 43.0 KiB 0 B (0.0%) 53.7 KiB ✅ Claude stale shell reloadrows27 27 0 (0.0%) 27 ✅ Claude stale shell resumeWebSocket decoded140 B 140 B 0 B (0.0%) 400 B ✅ Claude stale shell resumeWebSocket messages2 2 0 (0.0%) 2 ✅ Claude stale todo list cold openHTTP decoded73.4 KiB 73.4 KiB 0 B (0.0%) 83.0 KiB ✅ Claude stale todo list cold openrows75 75 0 (0.0%) 75 ✅ Claude stale todo list cold opennodes2 2 0 (0.0%) 4 ✅ Claude stale todo list cold opensubagents2 2 0 (0.0%) 4 ✅ Claude stale todo list cold openplans1 1 0 (0.0%) 2 ✅ Claude stale todo list cold openproviderTurns1 1 0 (0.0%) 2 ✅ Claude stale todo list cold opencheckpoints1 1 0 (0.0%) 2 ✅ Baseline:
afff0d3· PR result:6ac3247· Source CI: successScenario 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.
Approvability#
Verdict: Approved at
6ac3247Macroscope'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).Yash-Singh1(GitHub profile, opens in a new tab)replied on
apps/server/src/vcs/VcsStatusBroadcaster.tsline 489Note
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.
macroscopeapp[bot](GitHub profile, opens in a new tab) approved these changes
macroscopeapp[bot](GitHub profile, opens in a new tab)replied on
apps/server/src/vcs/VcsStatusBroadcaster.tsline 489The 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?
macroscopeapp[bot](GitHub profile, opens in a new tab) approved these changes
Yash-Singh1(GitHub profile, opens in a new tab)replied on
apps/server/src/vcs/VcsStatusBroadcaster.tsline 489Note
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.
Yash-Singh1(GitHub profile, opens in a new tab) merged this
274474dYash-Singh1(GitHub profile, opens in a new tab) closed this
Sign in to join this conversation.
macroscopeapp[bot](GitHub profile, opens in a new tab) reviewed