Skip to content

Commit 33cb900

Browse files
fix(server): local-only status refresh no longer wipes remote status
The branch menu's local-only refresh wrote a `value: null` remote entry when no remote was cached or the branch changed. Readers take that as "read and found no PR", so it overwrote a PR a concurrent first poll had just cached and made the turn-end PR refresh skip its read, including after a branch switch. The local-only path now refreshes local status and returns the cached parts. A branch change already drops the old branch's remote and publishes `remoteUpdated: null` from the local update, and every remote read invalidates GitManager's remote cache first, so the write, the invalidate and the remote write lock it waited behind are gone. Co-Authored-By: Claude Opus 5.5 <[email protected]>
1 parent 0782f95 commit 33cb900

2 files changed

Lines changed: 9 additions & 19 deletions

File tree

‎apps/server/src/vcs/VcsStatusBroadcaster.test.ts‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -375,17 +375,19 @@ describe("VcsStatusBroadcaster", () => {
375375
yield* Deferred.await(subscribed);
376376

377377
state.currentLocalStatus = { ...baseLocalStatus, refName: "main" };
378+
state.currentRemoteStatus = baseRemoteStatus;
378379
const refreshed = yield* broadcaster.refreshStatus("/repo", { localOnly: true });
379380
assert.equal(refreshed.refName, "main");
380381
assert.isNull(refreshed.pr);
381382
assert.equal(state.localInvalidationCalls, 1);
382383
assert.equal(state.remoteStatusCalls, 1);
383-
assert.equal(state.remoteInvalidationCalls, 2);
384+
assert.equal(state.remoteInvalidationCalls, 1);
384385
assert.deepStrictEqual((yield* Fiber.join(events)).slice(1), [
385386
{ _tag: "remoteUpdated", remote: null },
386387
{ _tag: "localUpdated", local: state.currentLocalStatus },
387388
]);
388389
assert.isNull((yield* broadcaster.getStatus({ cwd: "/repo" })).pr);
390+
assert.equal(state.remoteStatusCalls, 2);
389391
state.currentLocalStatus = { ...state.currentLocalStatus, hasWorkingTreeChanges: true };
390392
yield* broadcaster.refreshLocalStatus("/repo");
391393
state.currentLocalStatus = { ...baseLocalStatus, refName: "union" };
@@ -555,13 +557,14 @@ describe("VcsStatusBroadcaster", () => {
555557
yield* Deferred.succeed(releaseRemoteRead, undefined);
556558
yield* Fiber.join(remoteRefresh);
557559
yield* Fiber.join(localRefresh);
560+
state.currentRemoteStatus = baseRemoteStatus;
558561

559562
const status = yield* broadcaster.getStatus({ cwd: "/repo" });
560563
assert.equal(status.refName, "main");
561564
assert.isNull(status.pr);
562565
assert.equal(status.aheadCount, 0);
563566
assert.equal(status.behindCount, 0);
564-
assert.equal(state.remoteStatusCalls, 2);
567+
assert.equal(state.remoteStatusCalls, 3);
565568
}).pipe(Effect.provide(layerTestFor(state)));
566569
});
567570

‎apps/server/src/vcs/VcsStatusBroadcaster.ts‎

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -537,24 +537,11 @@ export const make = Effect.gen(function* () {
537537
"VcsStatusBroadcaster.refreshStatus",
538538
)(function* (rawCwd, options) {
539539
const cwd = yield* withFileSystem(normalizeCwd(rawCwd));
540-
const previous = options?.localOnly ? yield* getCachedStatus(cwd) : null;
541-
const local = yield* refreshLocalStatusCore(cwd);
540+
yield* refreshLocalStatusCore(cwd);
542541
if (options?.localOnly) {
543-
return yield* withRemoteWriteLock(
544-
cwd,
545-
Effect.gen(function* () {
546-
if (previous?.local?.value.refName !== local.refName || !previous?.remote) {
547-
yield* workflow.invalidateRemoteStatus(cwd);
548-
yield* updateCachedRemoteStatus(
549-
cwd,
550-
{ branch: local.refName, fingerprint: fingerprintStatusPart(null), value: null },
551-
{ publish: true },
552-
);
553-
}
554-
const parts = yield* getStatusParts(cwd);
555-
return mergeGitStatusParts(parts.local, parts.remote);
556-
}),
557-
);
542+
// Never cache a remote here: readers take `value: null` as "read and found no PR".
543+
const parts = yield* getStatusParts(cwd);
544+
return mergeGitStatusParts(parts.local, parts.remote);
558545
}
559546
return yield* withRemoteWriteLock(
560547
cwd,

0 commit comments

Comments
 (0)