# fix(trail): require branch push to origin before creating a trail

`dc40fb6`→[main](/content/gh/entireio/cli/commits/main/index.html)· dipree·4w ago·2 files·+37 added/-18 removed

Make delivering the branch to origin a precondition for 'entire trail create'. Previously the push was gated on the CLI having created the branch locally, so 'git checkout -b foo && entire trail create --branch foo' bound the trail without ever pushing foo. The server then had no remote branch to anchor to and backfilled it at the base tip, producing local/remote/work drift while still printing 'Created trail'.

Now the branch is always pushed (hard-failing on auth/non-fast-forward errors) and the create request sends branch_action=link so the server attaches the delivered branch instead of backfilling at base.

## Sessions

e0ad760f2e8eView transcript

## Changes

2

- cmd/entire/cli

- api

- Mtrail_types.go+16/-9

- Mtrail_cmd.go+21/-9

```go
// TrailCreateRequest is the body for POST /api/v1/trails/:host/:owner/:repo.
type TrailCreateRequest struct {
	Title      string   `json:"title"`
	Body       string   `json:"body,omitempty"`
	BranchName string   `json:"branch_name"`
	Base       string   `json:"base,omitempty"`
	Status     string   `json:"status,omitempty"`
	Assignees  []string `json:"assignees,omitempty"`
	Labels     []string `json:"labels,omitempty"`
	Priority   string   `json:"priority,omitempty"`
	Type       string   `json:"type,omitempty"`
	// BranchAction tells the server how to bind BranchName. The CLI always
	// pushes the branch to the remote before creating the trail, so it sends
	// "link" to attach the already-delivered branch. This prevents the server
	// from backfilling a backing branch at the base tip when the branch was
	// never delivered (which would silently anchor the trail to empty/diverged
	// content). Valid values: "create" (default, server-side) or "link".
	BranchAction string   `json:"branch_action,omitempty"`
}
```

Mcmd/entire/cli/api/trail_types.go+16/-9

```
641 unmodified lines
```

```go
if needsCreation {
	// needsCreation is a local-only signal, so the branch may still exist on
	// origin (e.g. a teammate pushed it and we never fetched). In that case the
	// remote branch is not ours, and our push may merely fast-forward it; we
	// must not delete it during cleanup. Only treat the remote branch as
	// created-by-us when it did not already exist before our push.
	// Delivering the branch to origin is a PRECONDITION for trail creation, not
	// an optional follow-up. A trail binds to a *remote* branch; if the branch
	// never reaches origin the server has nothing to anchor to and backfills it
	// at the base tip, producing a trail whose backing branch does not reflect
	// the user's work (local branch, remote branch, and actual work all differ).
	// We therefore always push and hard-fail on error, regardless of whether we
	// created the branch locally. (Previously this was gated on needsCreation,
	// so `git checkout -b foo && entire trail create --branch foo` created the
	// trail without ever pushing foo.)
	{
		// branchExistsOnOrigin tells us whether origin already has this branch
		// (e.g. a teammate pushed it, or we pushed it on a previous run). In that
		// case our push may merely fast-forward it and we must not delete it
		// during cleanup. Only treat the remote branch as created-by-us when it
		// did not already exist before our push.
		existedOnOrigin, existErr := branchExistsOnOrigin(branch)
		if existErr != nil {
			// Be conservative: if we cannot tell, do not delete the remote branch.
		}
		if err := pushBranchToOrigin(branch); err != nil {
			cleanupCreatedTrailBranch(repo, branch, localBranchCreated, false, errW)
			return fmt.Errorf("failed to push branch %q: %w", branch, err)
		}
	}
}
```

Mcmd/entire/cli/trail_cmd.go+21/-9
