JanDeDobbeleer/oh-my-posh · warning

gitstatus: reftables HEAD requires exec fallback

Error message

gitstatus: reftables HEAD requires exec fallback

What it means

resolveBranch reads HEAD directly from .git/HEAD text. With the new git 'reftable' ref-storage backend, HEAD contains the marker value 'reftablesHead' instead of a normal ref path, which this fast path cannot resolve. The error tells the caller (Load) to fall back to shelling out to git (exec) to resolve the branch.

Source

Thrown at src/gitstatus/refs.go:34

const (
	branchRefPrefix = "ref: refs/heads/"
	reftablesHead   = "ref: refs/heads/.invalid"
)

// resolveBranch reads HEAD and resolves it to a commit hash. For a branch
// checkout it also resolves the configured upstream and, when one exists,
// the ahead/behind counts. The returned bool is false only for an unborn
// branch (HEAD points at a branch ref that has never been committed to).
func resolveBranch(opts Options, cfg *ini.File, store *objectStore, result *Result) (plumbing.Hash, bool, error) {
	data, err := os.ReadFile(filepath.Join(opts.WorktreeGitDir, "HEAD"))
	if err != nil {
		return plumbing.ZeroHash, false, err
	}

	head := strings.TrimSpace(string(data))
	if head == reftablesHead {
		return plumbing.ZeroHash, false, errors.New("gitstatus: reftables HEAD requires exec fallback")
	}

	branchName, isBranch := strings.CutPrefix(head, branchRefPrefix)
	if !isBranch {
		return resolveDetached(head, result)
	}

	result.Ref = branchName

	hash, ok, err := resolveRef(opts.CommonGitDir, "refs/heads/"+branchName)
	if err != nil {
		return plumbing.ZeroHash, false, err
	}

	if !ok {
		// Unborn branch: no commits yet. Matches porcelain's
		// `# branch.oid (initial)`. Upstream/ahead-behind are left at their
		// zero values, same as the exec-git parity path for this case.

View on GitHub (pinned to 0976794618)

Solutions

  1. Catch this error and fall back to the exec-based branch resolution (git symbolic-ref / git branch --show-current)
  2. If you control the repo, migrate back to files backend: git refs migrate --ref-format=files
  3. Ensure the fallback path in Load is wired so this error surfaces as a normal prompt render, not a hard failure

Example fix

// before
hash, isBranch, err := resolveBranch(env, result)
if err != nil { return err }
// after
hash, isBranch, err := resolveBranch(env, result)
if err != nil {
    if errors.Is(err, errReftablesHead) {
        return resolveBranchViaExec(env, result)
    }
    return err
}
Defensive patterns

Strategy: fallback

Validate before calling

if data, _ := os.ReadFile(gitDir + "/HEAD"); strings.TrimSpace(string(data)) == "ref: reftable" {
    // plan for exec-based resolution up front
}

Type guard

func isReftablesHead(head string) bool {
    return strings.TrimSpace(head) == "reftable"
}

Try / catch

hash, isBranch, err := resolveBranch(env, result)
if err != nil {
    if strings.Contains(err.Error(), "reftables HEAD") {
        return resolveBranchViaExec(env, result)
    }
    return err
}

Prevention

When it happens

Trigger: Opening a repository whose refs are stored in the reftable format (git >= 2.45 repos initialized with reftable storage, e.g. via git init --ref-format=reftable or cloned with gitoxide/Jujutsu defaults).

Common situations: Using a repo created by Jujutsu or a recent git with reftable enabled; user upgrades git or migrates refs, and the oh-my-posh gitstatus fast path stops resolving HEAD.

Related errors


AI-assisted analysis of JanDeDobbeleer/oh-my-posh@0976794618 (2026-08-31). Data as JSON: /api/errors/d34b66346397cf69. Report an issue: GitHub.