gitbutlerapp/gitbutler · error
Remote HEAD for {remote_name} is not symbolic
Error message
Remote HEAD for {remote_name} is not symbolic What it means
The remote HEAD reference exists but head_ref.target().try_name() returned None, meaning refs/remotes/<remote>/HEAD is a plain ref pointing directly at an object id instead of symbolically aliasing refs/remotes/<remote>/<branch>. GitButler needs the symbolic form to derive the default target branch name, so default_target_setting_if_none bails.
Source
Thrown at crates/but-action/src/lib.rs:158
fn default_target_setting_if_none(ctx: &Context) -> anyhow::Result<()> {
if ctx.project_meta()?.target_ref.is_some() {
return Ok(());
}
// Lets do the equivalent of `git symbolic-ref refs/remotes/origin/HEAD --short` to guess the default target.
let repo = ctx.repo.get()?;
let remote_name = repo
.remote_default_name(gix::remote::Direction::Push)
.ok_or_else(|| anyhow::anyhow!("No push remote set or more than one remote"))?
.to_string();
let mut head_ref = repo
.find_reference(&format!("refs/remotes/{remote_name}/HEAD"))
.map_err(|_| anyhow::anyhow!("No HEAD reference found for remote {remote_name}"))?;
let target_ref_name = head_ref
.target()
.try_name()
.ok_or_else(|| anyhow::anyhow!("Remote HEAD for {remote_name} is not symbolic"))?
.to_owned();
let head_commit = head_ref.peel_to_commit()?;
ctx.set_project_meta(ProjectMeta {
target_ref: Some(target_ref_name),
target_commit_id: Some(head_commit.id),
push_remote: None,
})?;
Ok(())
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, EnumString, Default)]
#[serde(rename_all = "camelCase")]
pub enum ActionHandler {
#[default]
HandleChangesSimple,
}View on GitHub (pinned to 2497b8007a)
Solutions
- Recreate it as a symref: git update-ref -d refs/remotes/<remote>/HEAD && git remote set-head <remote> --auto.
- Or point it at the branch directly: git remote set-head <remote> <branch>.
- Or configure GitButler's target branch explicitly so the guess path is skipped.
- Check the current form with git symbolic-ref refs/remotes/<remote>/HEAD — it errors on non-symbolic refs.
Example fix
git update-ref -d refs/remotes/origin/HEAD git remote set-head origin --auto git symbolic-ref refs/remotes/origin/HEAD # -> refs/remotes/origin/main
Defensive patterns
Strategy: validation
Validate before calling
fn remote_head_is_symbolic(repo: &gix::Repository, remote: &str) -> bool {
repo.find_reference(&format!("refs/remotes/{remote}/HEAD"))
.ok()
.and_then(|r| r.target().try_name().map(|_| true))
.unwrap_or(false)
} Prevention
- Never write refs/remotes/<remote>/HEAD with update-ref; use git remote set-head.
- Validate the symref shape when onboarding a repo into GitButler.
- Prefer an explicitly configured target_ref over the discovery path.
When it happens
Trigger: default_target_setting_if_none on a repo where refs/remotes/<remote>/HEAD was written as a direct ref — e.g. via `git update-ref refs/remotes/origin/HEAD <sha>`, or created by mirror/backup tooling that materializes symrefs as plain refs.
Common situations: Repos restored from bundles or mirror clones where symrefs became direct refs; scripts pinning remote HEAD with update-ref; migration tooling copying refs verbatim.
Related errors
- No HEAD reference found for remote {remote_name}
- No push remote set or more than one remote
- Target branch {target_ref_name} not found
- No base branch configured
- DefaultTargetNotFound
AI-assisted analysis of gitbutlerapp/gitbutler@2497b8007a (2026-08-17).
Data as JSON: /api/errors/8a5b4832d128a246.
Report an issue: GitHub.