Skip to content

Find an incremental baseline by commit ancestry when the branch has none - #178

Merged
Ibrahimrahhal merged 5 commits into
mainfrom
cursor/ancestor-baseline-1a15
Sep 30, 2026
Merged

Ibrahimrahhal merged 5 commits into
mainfrom
cursor/ancestor-baseline-1a15

Conversation

@Ibrahimrahhal

@Ibrahimrahhal Ibrahimrahhal commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

A small percentage of pipelines are checked out with detached head (no branch is resolved) so we aren't able to locate the same branch base so I'm adding a fallback to try to identify a base using the commit history instead.

LLM desc:

Problem

The CLI only finds a baseline by branch name: the scanned branch, then origin/HEAD, then main or master. Two common cases find nothing, even when a recent ancestor commit was scanned:

  • Detached HEAD. This is how CI checkouts such as Harness or Drone usually land. The pipeline's own earlier scans were uploaded with a commit but a null branch, so a name lookup never matches them.
  • First scan of a branch whose trunk is not main or master. For example, a project whose trunk is develop. The trunk lookup never asks about develop, so the branch scans everything. Production data shows this is a significant share of full CLI scans.

The git-diff path also refused outright whenever branch was None, although it never used the branch.

Change

  • Local ancestry walk. Walks HEAD's nearest 10 commits breadth-first over every parent, so both sides of a merge count. A shallow clone stops at its depth. No API calls are involved.
  • One lookup by commit list. Sends GET /api/v1/scans?sha=<c1>,<c2>,... with the usual baseline filters (engine, complete, no PRs, full project state, clean when checksums are unusable) and no branch filter. Up to 3 pages of 50 are read, stopping at the first page with any candidate. Matches are re-checked client-side against the walked commits, because a backend that ignores sha returns scans of any commit.
  • Pick the nearest. Fewest commits back wins. Among scans of the same commit, stored checksums win, then recency. A run that cannot git diff only accepts ancestors with checksums. That decision comes from the same git_diff_refusal function that plan_git_diff refuses with, so a later change to when a git diff is allowed (such as Diff the working tree when it is dirty, instead of scanning everything #170) applies to both.
  • New lookup order. The branch's own scans come first, then ancestry, then trunk. Ancestry comes before trunk because a scan of the commit this branch forked from differs only by this branch's own work, while trunk's newest scan also carries whatever trunk did since the fork. On a detached HEAD, the branch step is skipped.
  • Backward compatible.
    • A backend without the comma-separated sha support reads the list as one literal commit and returns nothing.
    • If the lookup by commit fails outright (any error status or a network error), it is treated as no match and the trunk lookup still runs, so no project loses the baseline earlier releases found.
    • If the endpoint itself is down, the trunk lookup fails too and the run reports a failed lookup.
    • The "no baseline" message only mentions recent commits when the lookup by commit actually answered.
  • Git diff no longer needs a branch. It only needs HEAD's commit and the baseline's commit. A baseline with no commit now gets its own refusal message.
  • Output. The run names the baseline as scan of ancestor commit abc1234 (N commits back).

Backend: Corgea/doghouse#2235, now merged, lets sha take a comma-separated list, capped at 50 commits. A client-named baseline (incremental_base_sha or incremental_base_scan_id) is not matched against the upload's branch on the server side.

Tests

  • Unit tests over real temp repos:
    • The nearest ancestor is chosen over trunk, sibling and unfetched scans.
    • A scan of HEAD itself is zero commits back.
    • No match when nothing on the page is an ancestor.
    • The checksum tie-break.
    • Checksums are required when a git diff is impossible.
    • HEAD not in the clone.
    • The walk follows both parents of a merge and skips other branches.
    • The walk stops at the commit limit.
    • A git diff with no branch.
  • E2E:
    • The request order is updated for branch, then ancestry, then trunk.
    • A develop-trunk baseline found by ancestry, on both a named branch and a detached HEAD.
    • A detached HEAD reaches trunk through a git diff, both when the lookup by commit finds nothing and when it fails with a 400.
    • A failed branch lookup scans everything and says the lookup failed.
  • ./harness check passes: all tests, clippy and format.
Open in Web Open in Cursor 

A detached CI checkout reports no branch, so the baseline lookup asked only
about trunk and never found the pipeline's own earlier scans, which recorded
no branch either. When the checkout names no branch, pick the recent scan
whose commit is nearest behind HEAD (any branch, including none), then fall
back to the trunk lookup. The git diff no longer requires a branch name,
which it never used.

Co-authored-by: ibrahim <ibrahim@corgea.com>
@Ibrahimrahhal
Ibrahimrahhal marked this pull request as ready for review September 24, 2026 09:11
Comment thread src/incremental.rs
}
if let Some(scan) =
nearest_ancestor_baseline(&scans, ancestry, checksums_usable, git_diffable)
{

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

nitpick: Ancestor search stops before finding the nearest baseline

The function returns as soon as any page contains an ancestor. Since pages are ordered by scan creation time rather than commit distance, a later page can contain a much nearer ancestor. This contradicts the stated nearest-baseline behavior and can unnecessarily rescan many files. Track the best candidate across all searched pages before returning.

Proof or reproduction:

Page 1: scan(root, 100 commits back) -> immediately returned
Page 2: scan(parent_of_head, 1 commit back) -> never examined

@corgea-security corgea-security 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.

Automated review risk: 2/5.

The detached-HEAD baseline logic is sound overall, but pagination can select a substantially older baseline despite promising the nearest ancestor.

No critical or high-priority changes were found.

@corgea-security corgea-security added the dennis-reviewed Dennis completed an automated review label Sep 24, 2026

@corgea-security corgea-security 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.

Approved by Dennis: high policy risk and automated risk 2/5.

@cursor cursor 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.

Two ways a detached run still misses the baseline this change is aiming at, or picks a worse one than the trunk lookup it replaces.

Open in Web View Automation 

Sent by Cursor Automation: pr-flow

Comment thread src/incremental.rs
Comment thread src/incremental.rs Outdated
Comment thread src/incremental.rs Outdated
Comment thread src/incremental.rs
Comment thread src/incremental.rs Outdated
Walk HEAD's nearest 50 commits locally (breadth-first over every parent)
and ask the scan list for scans of any of them in one request, instead
of paging the project's recent scans and testing each for ancestry.
Matches are re-checked client-side for backends that ignore the list.

Runs after the branch's own lookup and before trunk's, so the first scan
of a branch whose trunk is not main or master diffs against the commit
it forked from, and a detached HEAD finds its pipeline's earlier runs.

Co-authored-by: ibrahim <ibrahim@corgea.com>
@cursor cursor Bot changed the title Find an incremental baseline by commit ancestry on a detached HEAD Find an incremental baseline by commit ancestry when the branch has none Sep 29, 2026
cursoragent and others added 3 commits September 29, 2026 13:41
…e and detached trunk fallback

The ancestor lookup decides whether a commit-only baseline is usable from
the same function plan_git_diff refuses with, so a change to when a git
diff is allowed cannot leave the two disagreeing.

E2E: a failed lookup by commit scans everything and says the lookup
failed, and a detached HEAD with no scanned recent commit reaches trunk.

Co-authored-by: ibrahim <ibrahim@corgea.com>
A backend that cannot answer a list of commits, or errors on it, must not
cost the trunk baseline earlier releases found. The failure is treated as
no match and the trunk lookup runs; if the endpoint itself is down, that
lookup fails and the run says so. The refusal only claims no recent commit
was scanned when the lookup by commit actually answered.

Co-authored-by: ibrahim <ibrahim@corgea.com>
Co-authored-by: ibrahim <ibrahim@corgea.com>
@Ibrahimrahhal
Ibrahimrahhal merged commit 547b2d3 into main Sep 30, 2026
18 checks passed
@Ibrahimrahhal
Ibrahimrahhal deleted the cursor/ancestor-baseline-1a15 branch September 30, 2026 08:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dennis-reviewed Dennis completed an automated review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants