Skip to content

Stop sending a guessed main as a trail's base - #2886

Merged
peyton-alt merged 4 commits into
mainfrom
peyton/trail-base-from-server
Oct 30, 2026
Merged

peyton-alt merged 4 commits into
mainfrom
peyton/trail-base-from-server

Conversation

@peyton-alt

@peyton-alt peyton-alt commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

https://entire.com/gh/entireio/-/trails/2514/changes/cli/1677

Problem

With no --base, entire trail create used the clone's origin/HEAD, and when the clone had none (origin added or re-pointed by hand, older git) it guessed main whenever origin/main existed. It sent that guess, so the server never applied the repo's real default branch (trail.base || repo.default_branch).

In a repo whose default isn't main:

  • the trail gets base main, Entire's draft targets main, and the user's own PR into the real default can't settle the trail (merge matching needs the same base) — it stays open and the draft keeps re-running CI. This is the root cause behind a customer report of a duplicate draft on already-merged work (entire-api trail 1094 guards a different path to the same symptom).
  • when the user is on the default branch, the wrong guess also makes the trail reuse the default branch itself, so the draft proposes merging the whole default branch into main.

Fix

Base = --base, else origin/HEAD, else origin's HEAD from git ls-remote --symref origin HEAD (10s bound, non-interactive, output parsed strictly and validated as a branch name), else nothing — the server then uses the repo's default branch. Both create paths (repo trails and project trails) use it. Branch naming keeps the old local main/master guess when the base is unknown; that guess is never sent.

The lookup is a package seam stubbed to "unknown" in TestMain, so no unit test reaches a real remote.

Evidence (live, real entire trail create)

Throwaway GitHub repo with default branch release, clone without origin/HEAD, on release:

installed nightly this branch
trail base main release
trail branch release (the default itself) base-fix-new-binary (from the title)
Entire's draft release → main base-fix-new-binary → release
after merging the PR — trail merged

git ls-remote --symref origin HEAD also verified against a private GitHub remote (release) and an entire:// remote (main).

Notes / follow-ups

  • A stale local origin/HEAD (default renamed after clone) still wins over the server, as before.
  • Other GetDefaultBranchName users (explain, import, resume) keep the old guess; out of scope (see ENT-129 duplication).
  • If the lookup fails and the user is on a non-main default with a title, naming may reuse the current branch and the server rejects it ("branch is base"); pass --branch or --base.

Testing

  • Unit: symref parsing, a local bare remote whose HEAD is release, unreachable remote, base selection (explicit / origin/HEAD / remote / unknown / invalid), and the project create payload omitting an unknown base.
  • mise run check passes (the claudecode timing test failed only under machine load and passes on rerun).

🤖 Generated with Claude Code


Note

Medium Risk
Changes default trail base sent to the API and branch naming inputs for trail create; wrong behavior could mis-target drafts or branches, but logic is bounded, validated, and well-tested.

Overview
Trail create no longer sends a guessed main as the API base when --base is omitted and the real default branch cannot be determined confidently.

Resolution is split into what goes to the server vs what drives branch naming. The sent base is --base, else local origin/HEAD, else git ls-remote --symref (10s timeout, strict parsing, branch-name validation), else empty so the server applies the repo default. The old local main/master guess is kept only for namingBase (e.g. resolveCreateBranch) and is never posted.

resolveTrailCreateBase moves to trail_base.go; entire trail create and project trail publish both use it. Package tests stub remote lookup in TestMain; new unit tests cover symref parsing, remote HEAD, and base-selection cases; project create tests expect an omitted base when lookup is unknown.

Reviewed by Cursor Bugbot for commit 6bd3aeb. Configure here.

With no --base, trail create took origin/HEAD and, when the clone had
none, guessed main whenever origin/main existed. It sent that guess, so
the server never applied the repo's real default branch. In a repo whose
default is develop or release, the trail got base main: Entire's draft
targeted main, and the user's own PR into the real default could not
settle the trail, which stayed open with the draft re-running CI. When
the user was on the default branch, the wrong guess also made the trail
reuse that branch itself, so the draft proposed merging the whole
default branch into main.

The base is now --base, else origin/HEAD, else origin's HEAD read with
`git ls-remote --symref` (bounded, non-interactive, validated), else
nothing: the server then uses the repo's default branch. Branch naming
keeps the local main/master guess when the base is unknown; that guess
is never sent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Entire-Checkpoint: 01M5R0EEXWT24BA9R6Z8PAWA45
@peyton-alt
peyton-alt requested a review from a team as a code owner October 24, 2026 22:28
Copilot AI balanced review requested due to automatic review settings October 24, 2026 22:28

Copilot AI 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.

🟡 Changes recommended

Unchecked local symref targets can still produce incorrect trail bases, and subprocess output capture remains unbounded.

2 open issues
What changed in this PR

This PR separates trail base selection from branch naming so trail creation no longer sends a guessed main that overrides the server’s repository default.

Changes:

  • Resolve the base from --base, local origin/HEAD, or a bounded remote lookup; otherwise omit it.
  • Apply the shared resolver to repository and project trails, keeping local guesses only for branch naming.
  • Add resolver tests and stub remote lookup in package tests.
File Description
cmd/​entire/​cli/​trail_cmd.go Separates the API base from branch-naming input.
cmd/​entire/​cli/​trail_base.go Adds shared local and remote default-branch resolution.
cmd/​entire/​cli/​trail_base_test.go Tests parsing, remote lookup, and base-selection precedence.
cmd/​entire/​cli/​project_trail_publish.go Applies separate base and naming values to project trails.
cmd/​entire/​cli/​project_trail_publish_test.go Expects an omitted base when detection is inconclusive.
cmd/​entire/​cli/​global_test.go Stubs remote default-branch lookup for package tests.

Review effort: Balanced

Comment thread cmd/entire/cli/trail_base.go Resolved
Comment thread cmd/entire/cli/trail_base.go Resolved
peyton-alt and others added 3 commits October 24, 2026 15:43
A hand-set origin/HEAD pointing outside refs/remotes/origin/ was sent
as a literal base; only a branch of origin now counts. The ls-remote
lookup capped its output only after git had written it all to memory;
both streams now go through limitedWriter, failing on stdout overflow
and dropping excess stderr.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Entire-Checkpoint: 01M5R1BFQK7004Y2PFCVX7DXYC
…HEAD

fetch leaves an existing origin/HEAD alone when the upstream default
changes and the old branch survives, so the local ref could still send
the wrong base. Ask origin first and use origin/HEAD only when origin
cannot answer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XJeHKftfKHD7hSi6ZxJww8
Entire-Checkpoint: 01M673VT0BAZJ62KE71WSQ43SY
@peyton-alt
peyton-alt merged commit 54b6a5c into main Oct 30, 2026
23 checks passed
@peyton-alt
peyton-alt deleted the peyton/trail-base-from-server branch October 30, 2026 19:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants