Skip to content

fix(project): detect the repo's default branch on add#209

Open
codebanditssss wants to merge 1 commit into
mainfrom
fix/project-detect-default-branch
Open

fix(project): detect the repo's default branch on add#209
codebanditssss wants to merge 1 commit into
mainfrom
fix/project-detect-default-branch

Conversation

@codebanditssss

Copy link
Copy Markdown
Collaborator

Closes #208.

Problem

ao project add never inspected the repo's branch, so Config.DefaultBranch stayed empty and WithDefaults() forced it to main. Any repo whose default branch isn't main (master, develop, trunk, …) then failed every ao spawn with a misleading BRANCH_NOT_FETCHED — and project add has no --branch flag, so there was no CLI workaround. This breaks the README quickstart (project addspawn) for a large class of repos.

Change

  • Add resolveDefaultBranch (mirrors resolveGitOriginURL: best-effort git symbolic-ref --short HEAD, never fails add).
  • In Service.Add (single-repo path), record the detected branch as DefaultBranch only when the user set none and it diverges from main — so the common main repo keeps an empty (NULL) config and behavior is unchanged there. A detached HEAD or git error falls back to the existing main default.

Test

  • TestManager_AddDetectsNonMainDefaultBranch: a master repo records master; an explicit config still wins.
  • Pinned the gitRepo test helpers to git init -b main so detection is deterministic regardless of the host's init.defaultBranch (this is also why two existing fixtures changed).
  • go build ./..., go vet ./..., and the full go test ./... suite pass.
  • Verified end-to-end against a live daemon: a fresh master repo now reports default branch: master on project add and ao spawn succeeds (idle session in zellij) where it previously failed BRANCH_NOT_FETCHED.

Scope note

Surgical to the single-repo path. Workspace-root projects already git init -b main, so they are unaffected. The misleading "run git fetch" wording in the BRANCH_NOT_FETCHED message is left as-is — with correct detection it no longer fires for this case.

Register the repo's actual checked-out branch as the project default so
session worktrees base off a ref that exists. Previously Config.DefaultBranch
was left empty and defaulted to "main", so a repo on master/develop/trunk
failed every spawn with BRANCH_NOT_FETCHED and had no CLI workaround.

Detection is best-effort (symbolic-ref --short HEAD); a detached HEAD or git
error falls back to the existing main default. Only persist when the branch
diverges from main, so the common main repo keeps a NULL config.

Closes #208
@greptile-apps

greptile-apps Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes ao project add so it detects a repo's actual default branch and persists it when the branch differs from main, preventing BRANCH_NOT_FETCHED failures on every subsequent ao spawn for repos whose default is master, trunk, develop, etc.

  • Adds resolveDefaultBranch (mirrors resolveGitOriginURL) that shells out to git symbolic-ref --short HEAD and stores the result only when it diverges from main, keeping the NULL config for the common case.
  • Adds TestManager_AddDetectsNonMainDefaultBranch and pins both gitRepo test helpers to git init -b main so detection is deterministic across hosts with different init.defaultBranch settings.

Confidence Score: 4/5

Safe to merge with one caveat: the branch-detection helper resolves the current checkout rather than the remote's canonical default, which can silently misconfigure projects added from a feature branch.

The fix works correctly for the targeted scenario — a repo whose initial/only branch is master — but git symbolic-ref --short HEAD returns whatever the user currently has checked out. A developer on feature/xyz of a main repo who runs project add would get feature/xyz stored as DefaultBranch, persisting a transient branch as the project default and reproducing a variant of the original spawn failure once that branch is deleted. The rest of the change (NULL-config preservation, test determinism pinning) is well-structured and low-risk.

backend/internal/service/project/service.go — specifically the resolveDefaultBranch implementation and its caller in Add.

Important Files Changed

Filename Overview
backend/internal/service/project/service.go Adds resolveDefaultBranch and wires it into the single-repo Add path; the helper uses git symbolic-ref --short HEAD (current checkout) rather than the remote-tracking default, so a user on a feature branch of a main repo will silently persist the wrong default branch.
backend/internal/service/project/service_test.go Adds gitRepoOnBranch helper and TestManager_AddDetectsNonMainDefaultBranch; pins existing helper to git init -b main for determinism; covers the happy path and explicit-config-wins case cleanly.
backend/internal/httpd/controllers/projects_test.go Pins gitRepo fixture to git init -b main for the same determinism reason; straightforward one-line change.

Sequence Diagram

sequenceDiagram
    participant CLI as ao CLI
    participant Svc as project.Service.Add
    participant Git as git (subprocess)
    participant Store as SQLite store

    CLI->>Svc: Add(path, config?)
    Svc->>Svc: normalizePath / validateID / conflict checks
    Svc->>Git: git -C path symbolic-ref --short HEAD
    Git-->>Svc: branch name (or error)
    alt "branch != "" && branch != "main" && config.DefaultBranch == """
        Svc->>Svc: "row.Config.DefaultBranch = branch"
    else "branch == "main" or error or explicit config set"
        Svc->>Svc: DefaultBranch stays "" (WithDefaults fills "main" at spawn)
    end
    Svc->>Git: git -C path remote get-url origin
    Git-->>Svc: origin URL (or "")
    Svc->>Store: UpsertProject(row)
    Store-->>Svc: ok
    Svc-->>CLI: "Project{DefaultBranch: branch}"
Loading

Reviews (1): Last reviewed commit: "fix(project): detect the repo's default ..." | Re-trigger Greptile

Comment on lines +236 to +242
func resolveDefaultBranch(path string) string {
out, err := exec.Command("git", "-C", path, "symbolic-ref", "--short", "HEAD").Output()
if err != nil {
return ""
}
return strings.TrimSpace(string(out))
}

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.

P1 symbolic-ref HEAD captures the current checkout, not the repo's default branch

git symbolic-ref --short HEAD resolves whatever branch the user currently has checked out, not the repository's canonical default. If a developer runs ao project add while on feature/new-ui of a main-default repo, feature/new-ui != "main" is true and that transient branch gets persisted as DefaultBranch. Every subsequent ao spawn then bases worktrees off feature/new-ui; once the branch is deleted after merging, all spawns break in exactly the same way as the original bug.

A more reliable source is the remote-tracking ref refs/remotes/origin/HEAD, which records the remote's default branch locally without a network round-trip (set by git clone or git remote set-head). Falling back to symbolic-ref HEAD only when that ref is absent would preserve the current improvement while avoiding the false-positive case.

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.

ao project add records main as default branch for non-main repos, breaking ao spawn

1 participant