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.
Solutions
- Catch this error and fall back to the exec-based branch resolution (git symbolic-ref / git branch --show-current)
- If you control the repo, migrate back to files backend: git refs migrate --ref-format=files
- 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
- Keep the exec fallback wired in Load at all times
- Test against repos created with git init --ref-format=reftable
- Watch for git version changes in ref-storage defaults
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
- gitstatus: staged adds and deletes may be renames…
- git config file not found
- gitstatus: delta target size mismatch
- gitstatus: truncated delta insert
- environment variable not set, do you have the posh-git…
AI-assisted analysis of JanDeDobbeleer/oh-my-posh@0976794618 (2026-08-31).
Data as JSON: /api/errors/d34b66346397cf69.
Report an issue: GitHub.
Appendix: 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)