Skip to content

fix: wait for PR status before inactivity auto-settle - #5187

Open
caezium wants to merge 7 commits into
pingdotgg:mainfrom
caezium:agent/unknown-pr-auto-settle
Open

fix: wait for PR status before inactivity auto-settle#5187
caezium wants to merge 7 commits into
pingdotgg:mainfrom
caezium:agent/unknown-pr-auto-settle

Conversation

@caezium

@caezium caezium commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

What Changed

  • Preserve whether remote PR status is resolved across the VCS status stream, including initial snapshots, provider lookup fallbacks, typed failures, defects, and recovery.
  • Treat unresolved PR status as an inactivity auto-settle blocker in the shared settlement calculation used by Sidebar v2, ChatView, and the mobile thread list.
  • Key cached client PR state by branch, and use bounded, rotating web and mobile lookup pools that revisit hidden/off-screen branches with isolated short-lived subscriptions, without changing cache behavior elsewhere.
  • Correlate remote results and failures with the local ref generation, discard late results from an older branch, avoid returning unavailable remote data to direct callers, and keep retrying unresolved lookups even when automatic Git fetching is disabled.

Why

#5151 correctly keeps threads with open PRs active, but the client previously flattened “remote PR lookup has not resolved” into pr: null. During initial loading, after a stream error, or after a branch change, a stale thread could therefore pass the inactivity window before the client knew whether it had an open PR.

This follow-up makes “unknown” an explicit conservative state. Confirmed no-PR branches still use normal inactivity settlement, open PRs remain active, and merged/closed PR behavior is unchanged.

This is independent of #5141: that PR controls whether confirmed merged/closed PRs settle, while this PR prevents unresolved status from being mistaken for a confirmed no-PR result.

Validation

  • pnpm exec vp test run apps/server/src/vcs/VcsStatusBroadcaster.test.ts apps/server/src/git/GitManager.test.ts packages/shared/src/git.test.ts packages/client-runtime/src/state/threadSettled.test.ts packages/client-runtime/src/state/vcs.test.ts apps/mobile/src/features/threads/threadListV2.test.ts --reporter=dot — 337 passed
  • Typechecks passed for contracts, shared, client runtime, server, web, and mobile
  • Targeted lint, formatting, mobile native static analysis, and git diff --check passed for all changed files
  • All VCS scenarios use temporary repositories and mocked provider responses; no real repositories, PRs, or user data are used

Checklist

  • This PR is focused on one issue
  • I explained what changed and why
  • Screenshots are not applicable because this changes classification timing, not rendered UI
  • A video is not applicable because there are no animation or interaction changes

Built with GPT-5.6-Sol via the Codex harness in T3 Code.


Note

Medium Risk
Touches thread settlement classification, VCS status caching/stream semantics, and background PR polling across server, web, and mobile; incorrect remoteLoaded handling could keep threads active too long or settle them early.

Overview
Threads with a branch were sometimes auto-settled on inactivity while remote PR status was still loading or failed, because unresolved lookups were treated like no PR. This PR introduces an explicit unknown change-request state and blocks inactivity auto-settle until PR status is confirmed.

Server & stream: VCS status streams now carry remoteLoaded (and prLookupFailed on remote results). VcsStatusBroadcaster tracks ref generation, marks remote as unloaded on branch changes, refresh failures, and provider lookup fallbacks, and ignores stale remote updates after a ref change.

Clients: Shared resolveChangeRequestSettlementState and branch-keyed state (threadChangeRequestStateKey) feed effectiveSettled on web (Sidebar v2, ChatView) and mobile thread list v2. Off-screen branches get bounded rotating lookup pools (16 targets / 30s) with threadVcsEnvironment (statusIdleTtlMs: 0) so subscriptions do not pile up.

Confirmed no-PR, open, merged, and closed behavior is unchanged once status is resolved.

Reviewed by Cursor Bugbot for commit 96dc224. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Keep threads with unresolved PR status active instead of auto-settling

  • Adds a remoteLoaded flag to VCS status stream events and cached state so consumers can distinguish "remote not yet fetched" from "no PR"; threads with a branch default to unknown until their PR state is confirmed.
  • Extends effectiveSettled to treat unknown change request state as active (not settled), preventing premature auto-settlement while PR status loads.
  • Introduces resolveChangeRequestSettlementState to derive open|closed|merged|none|unknown by correlating thread branch, git status, and remoteLoaded.
  • Adds a background lookup pool (ThreadListV2ChangeRequestLookupPool / SidebarV2ChangeRequestStateReporter) that rotates through off-screen threads in bounded windows (limit 16, 30s interval) to refresh PR state independently of row virtualization.
  • Introduces threadVcsEnvironment with statusIdleTtlMs: 0 so status subscriptions are disposed immediately when idle, avoiding lingering background polling.
  • Behavioral Change: threads with a non-null branch now remain in the active block until their PR state resolves to a known value rather than being classified as settled on inactivity.

Macroscope summarized 96dc224.

Summary by CodeRabbit

  • Bug Fixes

    • Threads now remain active while pull request or merge request status is loading, unavailable, or uncertain.
    • Improved handling of failed, stale, and interrupted source-control status updates.
    • Preserved the last known review status when a lookup temporarily fails.
    • Correctly settle threads when changes are confirmed merged or closed, while avoiding premature settlement.
  • Documentation

    • Updated source-control guidance to explain thread behavior during status loading.

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds branch-aware change-request settlement states, propagates VCS loading and lookup failures, centralizes mobile lookups, and updates web and mobile thread settlement behavior. Tests cover failures, stale refreshes, branch changes, lookup windows, and unresolved states.

Changes

Change-request settlement

Layer / File(s) Summary
VCS status contracts and propagation
packages/contracts/src/git.ts, packages/shared/src/git.ts, packages/shared/src/git.test.ts, packages/client-runtime/src/state/vcs.ts, packages/client-runtime/src/state/vcs.test.ts, apps/mobile/src/state/vcs.ts
VCS status events now carry remoteLoaded, and remote results can report prLookupFailed. Stream accumulation tracks remote availability across snapshots and ref changes.
Server refresh and stale-result handling
apps/server/src/git/GitManager.ts, apps/server/src/git/GitManager.test.ts, apps/server/src/vcs/VcsStatusBroadcaster.ts, apps/server/src/vcs/VcsStatusBroadcaster.test.ts
Server refreshes retain fallback data, mark failed remote results unresolved, reject stale reference generations, retry unavailable remote data, and expose the resulting stream state.
Branch-aware settlement resolution
packages/client-runtime/src/state/threadSettled.ts, packages/client-runtime/src/state/threadSettled.test.ts
Shared helpers add keyed settlement state, branch-aware cache keys, and none/unknown handling. Unknown branched threads remain unsettled.
Mobile lookup pool integration
apps/mobile/src/features/threads/threadListV2.ts, apps/mobile/src/features/threads/thread-list-v2-items.tsx, apps/mobile/src/state/use-thread-pr.ts, apps/mobile/src/features/home/HomeScreen.tsx, apps/mobile/src/features/threads/ThreadNavigationSidebar.tsx, apps/mobile/src/features/threads/threadListV2.test.ts
Mobile lists group eligible targets by environment and working directory, rotate lookup windows, and centrally report settlement state to screen and sidebar state.
Web settlement integration
apps/web/src/components/ChatView.tsx, apps/web/src/components/SidebarV2.tsx, docs/user/source-control.md
Web settlement uses VCS-aware change-request resolution, branch-aware cache keys, centralized state updates, and loading-state behavior documented for Sidebar v2.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GitManager
  participant VcsStatusBroadcaster
  participant ClientVcsState
  participant ThreadSettlement
  GitManager->>VcsStatusBroadcaster: return PR status and lookup failure state
  VcsStatusBroadcaster->>ClientVcsState: publish remoteLoaded status
  ClientVcsState->>ThreadSettlement: resolve branch-aware change-request state
  ThreadSettlement-->>ClientVcsState: return none, unknown, open, closed, or merged
Loading

Possibly related PRs

Suggested reviewers: juliusmarminge, t3dotgg

🚥 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
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.
Title check ✅ Passed The title clearly summarizes the main change: preventing inactivity auto-settlement before PR status resolves.
Description check ✅ Passed The description covers the required change, rationale, UI applicability, validation, and checklist items.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 1, 2026
Comment thread apps/mobile/src/features/threads/ThreadNavigationSidebar.tsx
Comment thread apps/server/src/vcs/VcsStatusBroadcaster.ts Outdated
Comment thread apps/mobile/src/features/home/HomeScreen.tsx
Comment thread packages/client-runtime/src/state/threadSettled.ts Outdated
@caezium
caezium force-pushed the agent/unknown-pr-auto-settle branch from 9d94d18 to 0bf23d2 Compare August 1, 2026 14:18
@github-actions github-actions Bot added size:XL 500-999 changed lines (additions + deletions). and removed size:L 100-499 changed lines (additions + deletions). labels Aug 1, 2026
Comment thread apps/server/src/vcs/VcsStatusBroadcaster.ts
Comment thread apps/mobile/src/state/vcs.ts
@caezium
caezium marked this pull request as ready for review August 1, 2026 16:01
@caezium

caezium commented Aug 1, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

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.

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 3c7a1ad. Configure here.

Comment thread apps/web/src/components/SidebarV2.tsx
@macroscopeapp

macroscopeapp Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces significant runtime behavior changes to thread auto-settlement, keeping branch-backed threads active until their PR status is definitively resolved. It adds new state management across web, mobile, and server components, introduces a pooled PR lookup system, and modifies server-side VCS status caching. The cross-cutting scope and behavioral impact warrant human review.

You can customize Macroscope's approvability policy. Learn more.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/client-runtime/src/state/vcs.test.ts (1)

116-128: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add the omitted-option case to protect the conditional spread.

The test proves that statusIdleTtlMs: 0 reaches idleTTL. It does not prove that omitting the option keeps the previous default. The conditional spread in packages/client-runtime/src/state/vcs.ts at Line 275 exists only for that case. A regression that passes idleTtlMs: undefined unconditionally would not fail any test.

Assert the default TTL for a call without options.

💚 Proposed additional assertion
   const atoms = createVcsEnvironmentAtoms(runtime, { statusIdleTtlMs: 0 });
 
   expect(
     atoms.status({
       environmentId: TARGET.environmentId,
       input: { cwd: "/repo" },
     }).idleTTL,
   ).toBe(0);
+
+  const defaultAtoms = createVcsEnvironmentAtoms(runtime);
+  expect(
+    defaultAtoms.status({
+      environmentId: TARGET.environmentId,
+      input: { cwd: "/repo" },
+    }).idleTTL,
+  ).not.toBe(0);
 });
🤖 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 `@packages/client-runtime/src/state/vcs.test.ts` around lines 116 - 128, Add a
second assertion in the existing “allows clients to dispose VCS status streams
immediately” test that calls createVcsEnvironmentAtoms without statusIdleTtlMs
and verifies atoms.status(...).idleTTL retains the default TTL. Keep the current
zero-value assertion unchanged.
apps/web/src/components/SidebarV2.tsx (1)

426-426: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Rename the threadKey parameter to stateKey.

The value passed to onChangeRequestState is now the branch-aware state key from threadChangeRequestStateKey (line 557), not a scoped thread key. Line 451 in the same file binds threadKey to scopedThreadKey(threadRef), so one name now carries two different key formats. A future caller could pass the line 451 value and silently break the partition lookup at line 1588. The mobile prop already uses stateKey (apps/mobile/src/features/threads/thread-list-v2-items.tsx line 318).

♻️ Proposed rename
-  onChangeRequestState: (threadKey: string, state: ChangeRequestSettlementState) => void;
+  onChangeRequestState: (stateKey: string, state: ChangeRequestSettlementState) => void;
   const handleChangeRequestState = useCallback(
-    (threadKey: string, state: ChangeRequestSettlementState) => {
+    (stateKey: string, state: ChangeRequestSettlementState) => {
       setChangeRequestStateByKey((current) =>
-        updateChangeRequestSettlementState(current, threadKey, state),
+        updateChangeRequestSettlementState(current, stateKey, state),
       );
     },
     [],
   );

Also applies to: 1364-1365

🤖 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 `@apps/web/src/components/SidebarV2.tsx` at line 426, Rename the
onChangeRequestState parameter from threadKey to stateKey throughout its
declaration and all related call sites, including the handler around line 1364,
so it consistently represents the branch-aware key from
threadChangeRequestStateKey. Keep the separate threadKey binding derived from
scopedThreadKey(threadRef) unchanged.
🤖 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 `@apps/server/src/vcs/VcsStatusBroadcaster.ts`:
- Around line 505-509: Update the remote status refresh flow around
getCachedStatus, updateCachedRemoteStatus, and consecutiveFailuresRef so results
with prLookupFailed: true or loaded: false increment the consecutive failure
count instead of resetting it. Apply remoteRefreshFailureDelay using that count,
allowing unresolved lookups to back off toward
VCS_STATUS_REFRESH_FAILURE_MAX_DELAY while preserving recovery behavior when a
lookup succeeds.

---

Nitpick comments:
In `@apps/web/src/components/SidebarV2.tsx`:
- Line 426: Rename the onChangeRequestState parameter from threadKey to stateKey
throughout its declaration and all related call sites, including the handler
around line 1364, so it consistently represents the branch-aware key from
threadChangeRequestStateKey. Keep the separate threadKey binding derived from
scopedThreadKey(threadRef) unchanged.

In `@packages/client-runtime/src/state/vcs.test.ts`:
- Around line 116-128: Add a second assertion in the existing “allows clients to
dispose VCS status streams immediately” test that calls
createVcsEnvironmentAtoms without statusIdleTtlMs and verifies
atoms.status(...).idleTTL retains the default TTL. Keep the current zero-value
assertion unchanged.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a5129ee6-4901-45fa-8a2e-12c81b642bab

📥 Commits

Reviewing files that changed from the base of the PR and between d303706 and 3c7a1ad.

📒 Files selected for processing (21)
  • apps/mobile/src/features/home/HomeScreen.tsx
  • apps/mobile/src/features/threads/ThreadNavigationSidebar.tsx
  • apps/mobile/src/features/threads/thread-list-v2-items.tsx
  • apps/mobile/src/features/threads/threadListV2.test.ts
  • apps/mobile/src/features/threads/threadListV2.ts
  • apps/mobile/src/state/use-thread-pr.ts
  • apps/mobile/src/state/vcs.ts
  • apps/server/src/git/GitManager.test.ts
  • apps/server/src/git/GitManager.ts
  • apps/server/src/vcs/VcsStatusBroadcaster.test.ts
  • apps/server/src/vcs/VcsStatusBroadcaster.ts
  • apps/web/src/components/ChatView.tsx
  • apps/web/src/components/SidebarV2.tsx
  • docs/user/source-control.md
  • packages/client-runtime/src/state/threadSettled.test.ts
  • packages/client-runtime/src/state/threadSettled.ts
  • packages/client-runtime/src/state/vcs.test.ts
  • packages/client-runtime/src/state/vcs.ts
  • packages/contracts/src/git.ts
  • packages/shared/src/git.test.ts
  • packages/shared/src/git.ts

Comment on lines +505 to +509
const cachedRemoteLoaded = (yield* getCachedStatus(cwd))?.remoteLoaded ?? false;
const needsInitialRefresh = (yield* Ref.get(needsInitialRefreshRef)) || !cachedRemoteLoaded;
if (needsInitialRefresh) {
yield* Ref.set(needsInitialRefreshRef, true);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Apply backoff when a lookup stays unresolved without failing.

A provider lookup can return successfully and still report prLookupFailed: true. updateCachedRemoteStatus then returns loaded: false, so Line 534 keeps needsInitialRefreshRef true and Line 535 resets consecutiveFailuresRef to 0. The failure backoff in remoteRefreshFailureDelay never applies to this path.

The retry cadence therefore stays at activeInterval indefinitely. For a repository with missing or revoked hosting-provider credentials, prLookupFailed is persistent, so the server polls the provider API at the base interval forever. Lines 505-509 also keep this retry active when automatic fetching is disabled, which widens the affected set to users who opted out of periodic fetching.

The existing test at Lines 787-827 covers recovery after one unresolved result. It does not cover a persistently unresolved lookup.

Count consecutive unresolved results the same way as failures, so the delay grows toward VCS_STATUS_REFRESH_FAILURE_MAX_DELAY.

♻️ Proposed backoff for unresolved results
         if (Exit.isSuccess(exit)) {
-          yield* Ref.set(needsInitialRefreshRef, !exit.value.loaded);
-          yield* Ref.set(consecutiveFailuresRef, 0);
-          return activeInterval;
+          yield* Ref.set(needsInitialRefreshRef, !exit.value.loaded);
+          if (exit.value.loaded) {
+            yield* Ref.set(consecutiveFailuresRef, 0);
+            return activeInterval;
+          }
+          const unresolvedAttempts = yield* Ref.updateAndGet(
+            consecutiveFailuresRef,
+            (count) => count + 1,
+          );
+          return remoteRefreshFailureDelay(unresolvedAttempts, activeInterval);
         }

Also applies to: 534-534

🤖 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 `@apps/server/src/vcs/VcsStatusBroadcaster.ts` around lines 505 - 509, Update
the remote status refresh flow around getCachedStatus, updateCachedRemoteStatus,
and consecutiveFailuresRef so results with prLookupFailed: true or loaded: false
increment the consecutive failure count instead of resetting it. Apply
remoteRefreshFailureDelay using that count, allowing unresolved lookups to back
off toward VCS_STATUS_REFRESH_FAILURE_MAX_DELAY while preserving recovery
behavior when a lookup succeeds.

Comment thread apps/server/src/vcs/VcsStatusBroadcaster.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL 500-999 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant