Skip to content

feat: daemon auto-tracking of open PR branches#364

Merged
ScriptedAlchemy merged 1 commit into
masterfrom
codex/pr-branch-autotrack
Jul 9, 2026
Merged

feat: daemon auto-tracking of open PR branches#364
ScriptedAlchemy merged 1 commit into
masterfrom
codex/pr-branch-autotrack

Conversation

@ScriptedAlchemy

Copy link
Copy Markdown
Owner

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

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>
@changeset-bot

changeset-bot Bot commented Jul 9, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: cb6cb38

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 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".

Comment on lines +398 to +399
if state.managed.contains_key(label) {
continue; // idempotent: already tracked

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge 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 👍 / 👎.

Comment on lines +541 to +542
"-B",
label,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge 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 {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

@ScriptedAlchemy ScriptedAlchemy merged commit 5e800f1 into master Jul 9, 2026
13 of 14 checks passed
ScriptedAlchemy added a commit that referenced this pull request Jul 9, 2026
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant