feat: daemon auto-tracking of open PR branches#364
Conversation
Adds an opt-in daemon mode that discovers open PRs on a repo's GitHub origin and tracks/untracks each PR head branch through the existing branch-tracking machinery, so branch_diff/branch_search/branch_list and graph queries work against every open PR without manual tracking. - config: sync.auto_track_pr_branches (default off) + auto_track_pr_poll_secs (default 300, floor 60), env overrides, serde back-compat. - discovery: gh pr list when available + GitHub origin, else git ls-remote refs/pull/*/head with SHA-matching to origin heads. Same-repo PRs only; fork PRs skipped with a logged reason. - lifecycle: fetch each PR head, check it out into a linked worktree on a synthetic pr/<N> branch, track via add_branch_tracking; untrack + clean the per-branch store on PR close/merge. Idempotent; capped at 10 new tracks per cycle. - CLI: tracedecay branch autotrack status|enable|disable. - dashboard: Settings toggle + poll interval + tracked-PR-branch list. - observability: event=pr_autotrack action=tracked|untracked|skipped|poll. - tests: config round-trip, discovery parsers, reconcile removal/idempotency, and a fixture-repo lifecycle test (track→index→idempotent→untrack→cap). - docs: BRANCHING-USER-GUIDE PR auto-tracking section. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cb6cb38905
ℹ️ About Codex in GitHub
Your team has set up Codex to 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 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if state.managed.contains_key(label) { | ||
| continue; // idempotent: already tracked |
There was a problem hiding this comment.
Refresh managed PRs after head updates
For a PR that receives new commits after its first poll, the label remains pr/<N>, so this branch exits before any fetch or sync happens. Since track_pr is the path that fetches refs/pull/<N>/head and syncs the PR worktree, branch_search/branch_diff keep serving the old PR graph until the state is manually cleared or the PR is recreated.
Useful? React with 👍 / 👎.
| "-B", | ||
| label, |
There was a problem hiding this comment.
Avoid resetting user branches named pr/N
When a repo already has a local branch named like pr/123, this git worktree add -B overwrites it with the PR head; I checked git worktree add -h, where -B is documented as creating or resetting a branch. Cleanup later also runs git branch -D for the same label, so enabling auto-track can overwrite and delete a user's own branch on a synthetic-name collision.
Useful? React with 👍 / 👎.
| cleanup_pr_worktree(repo_root, &worktree, &tracking_ref, &label); | ||
| Err("project not indexed".to_string()) | ||
| } | ||
| Ok(_) => Ok(ManagedPr { |
There was a problem hiding this comment.
Do not record deferred branch tracking as managed
If add_branch_tracking_with_options returns Deferred because the branch-add/sync lock is busy, this wildcard still persists the PR as managed. In the pre-creation deferred case no branch DB exists, and in the post-creation deferred case it has not been synced, but later reconciles skip it because state.managed contains the label, leaving that PR unindexed or stale indefinitely.
Useful? React with 👍 / 👎.
Address 7 adversarially-confirmed findings in daemon PR-branch auto-tracking (adversarial review on #364/#367): 1. [major] Failed discovery is now distinguishable from zero open PRs. discover_open_prs returns Result; a git/gh command failure surfaces as Err and poll_project skips the whole reconcile cycle (logging outcome=error) instead of reconciling against an empty discovery and mass-untracking every managed PR. 2. [major] Interrupted track + advanced head no longer wedges forever. prepare_pr_worktree drops the erroring show-ref guard and resets the collision-proof synthetic branch with `worktree add -B`, adopting an orphan left by a mid-track crash. Orphan worktrees are swept on each (complete) reconcile so stale checkouts don't leak. 3. [major] remove_tracked_branch_store now serializes on the branch-add lock (matching prepare/gc) so it can't race a concurrent `branch add` and clobber or resurrect a branch-meta entry. 4. [minor] Per-cycle new-track cap uses `continue` not `break`, so an already-managed PR whose head advanced is still refreshed even when new tracks are capped. 5. [minor] All spawned git/gh subprocesses set GIT_TERMINAL_PROMPT=0 and GIT_ASKPASS=echo so a credential prompt can't hang the single poll loop. 6. [minor] gh discovery raises --limit and flags the result `partial` when it reaches the limit; partial discoveries suppress removals so PRs beyond the page aren't untracked as closed. 7. [minor] Disabling the setting now tears down managed PR state: the daemon runs a removals-only reconcile once (teardown_disabled_project) and the disable CLI output documents it. Tests: extend daemon_suite/pr_autotrack_test.rs and pr_autotrack/tests.rs covering failed discovery, the wedged-track recovery, the cap head-update starvation, disable teardown, the branch-add lock, and partial-discovery removal suppression. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Opt-in daemon mode (config + CLI + dashboard Settings toggle, default off) that discovers open PRs on the GitHub origin (gh when available, git ls-remote refs/pull/*/head fallback) and tracks each same-repo PR head through the existing branch-tracking machinery: fetched to refs/tracedecay/pr/, checked out into a linked worktree on a synthetic pr/ branch, indexed like any tracked branch, and untracked with full store/worktree cleanup on close or merge. Capped ramp (10/cycle), idempotent reconcile, fork PRs skipped with logged reasons, structured event=pr_autotrack logging, 60s poll floor. Fixture-repo lifecycle test covers track/idempotence/untrack/cap; dashboard build + vitest green; CI-exact clippy clean.
🤖 Generated with Claude Code