feat(staged): centralize PR polling with tiered intervals and PR recovery#650
feat(staged): centralize PR polling with tiered intervals and PR recovery#650
Conversation
Each BranchCardPrButton ran its own setInterval for PR status polling, leading to N concurrent gh CLI processes, stuck refreshing guards, and leaked event listeners. This replaces that with a single PrPollingService that coordinates all PR status refreshes via the existing backend refreshAllPrStatuses command. Key changes: - New PrPollingService with single timer, adaptive intervals, window focus awareness, concurrency protection, and stale-data tracking - Fix event listener race condition by awaiting listen() promises in cleanup instead of fire-and-forget .then() - Remove prStatusRefreshing guard, per-component setInterval, and window focus/blur handlers from BranchCardPrButton - Add stale-data indicator when polling fails repeatedly Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…component Add [PrPolling] prefixed console.info logs to help diagnose PR status update failures. Logs cover track/untrack, poll start/skip/end, scheduleNext intervals, refreshNow accept/drop, window focus/blur, pr-status-changed events, and $effect lifecycle in the component. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The create_pr and push_branch commands now emit a "running" session event (with branch_id, project_id, and session_type) before returning the session ID. This ensures the global sessionStatusListener registers the session before any completion event can arrive, fixing the race where fast-completing sessions hit "unknown session ID" warnings and PR numbers are never persisted. Remove the now-redundant sessionRegistry.register() and projectStateStore.addRunningSession() calls from the component's .then() callbacks. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a branch has been pushed but has no prNumber (e.g. due to a race condition during PR creation), the BranchCardPrButton now checks GitHub for an existing open PR on mount via `gh pr view <branch>`. This runs async so the frontend isn't blocked. If a PR is found, the number is persisted and polling starts immediately. New backend command `recover_branch_pr` wraps the existing `get_pr_for_branch` GitHub CLI helper and updates the branch record. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… component The info-level console.info logs were added to diagnose PR status update failures and are no longer needed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The polling service now refreshes all projects, not just the one being viewed. The selected project polls at the existing 60s interval, while background projects poll every 5 minutes. Projects with pending CI checks still use the faster 15s interval regardless of selection. Key changes: - Replace branch-level track/untrack API with project-level setProjects and setSelectedProject, driven by reactive $effects in App.svelte - Per-project lastPolledAt tracking so each project polls independently at its own interval via a single adaptive timer - Remove component-driven track/untrack from BranchCardPrButton; stale notifications now keyed by projectId instead of branchId Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…anup Resolve code review feedback on the centralized PR polling service: - poll(): remove scheduleNext() from the early-return path when refreshInFlight is true — the in-flight operation's finally block already reschedules, avoiding a potential busy-wait cycle - setProjects(): short-circuit when the set of project IDs hasn't changed, preventing unnecessary poll triggers from Svelte $effect re-fires - refreshNow(): queue the projectId when a refresh is already in-flight instead of silently dropping it, so post-PR-creation and post-push refreshes are never lost - Stale notification: use exact equality (=== MAX_CONSECUTIVE_FAILURES) so the callback fires only on the transition to stale, not every subsequent failure cycle - Move unsubStale from onMount/onDestroy to $effect with cleanup, making it immune to double-mount (HMR, keyed re-render) and automatically reactive to branch.projectId changes - Add shouldAttemptRecovery() guard in the polling service to prevent N concurrent `gh pr view` CLI calls when many BranchCardPrButton components mount simultaneously for branches without PR numbers
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c7abde0b8d
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| session_runner::emit_session_running( | ||
| &app_handle, | ||
| &session.id, | ||
| &branch_id, | ||
| &branch.project_id, | ||
| "pr", | ||
| ); |
There was a problem hiding this comment.
Emit running status only after session start succeeds
This emits a running session event before start_session(...) can fail, so if startup fails (for example provider/driver initialization errors), the command returns an error but the frontend has already registered the session as running via the global listener and may never receive a terminal event to clean it up. That leaves project/session UI stuck in a running state for PR creation (and the same ordering is repeated in the push path below).
Useful? React with 👍 / 👎.
| refreshInFlight = false; | ||
| scheduleNext(); |
There was a problem hiding this comment.
Drain queued immediate refreshes after scheduled polls
refreshNow() queues pendingRefreshProjectId whenever refreshInFlight is true, but this path only gets drained in refreshNow(...).finally. When the in-flight operation is poll(), execution reaches this block and schedules the next timer without consuming the queued project, so immediate refresh requests that happen during a scheduled poll are silently deferred until the normal interval.
Useful? React with 👍 / 👎.
Summary
BranchCardPrButtonwith a centralizedprPollingServicethat polls all projects app-wide with tiered intervals (15s for pending CI, 60s for selected project, 5min for background projects)recover_branch_prbackend command to look up existing open PRs on GitHub for branches missing PR numbers, with frontend coordination to prevent concurrentgh pr viewcallscreate_prandpush_branchcommandsTest plan
gh pr viewcalls when multiple branch cards mount simultaneously🤖 Generated with Claude Code