gitbutlerapp/gitbutler · error

Remote HEAD for is not symbolic

Error message

Remote HEAD for {remote_name} is not symbolic

What it means

refs/remotes/<remote>/HEAD must be a symbolic reference pointing at the remote's default branch (e.g. refs/remotes/origin/main). default_target_setting_if_none calls try_name() on the ref's target and throws this error if the ref exists but is not symbolic — i.e. it points directly at an object ID or holds an unexpected target kind — so the default branch name cannot be extracted.

Solutions

  1. Recreate it as a symbolic ref: `git remote set-head origin --auto` (or `--set` to a branch).
  2. Alternatively update the ref directly: `git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/main`.
  3. Configure the GitButler target branch explicitly so the symbolic remote HEAD is not needed.

Example fix

// before: HEAD is detached at an OID
$ git rev-parse refs/remotes/origin/HEAD
abc123...

// after
$ git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/main
Defensive patterns

Strategy: validation

Validate before calling

let is_sym = gix::refs::Reference::lookup("refs/remotes/origin/HEAD", repo)
    .map(|r| r.target().try_name().is_some())
    .unwrap_or(false);
if !is_sym {
    // repair: git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/main
}

Try / catch

match default_target_setting_if_none(ctx) {
    Err(e) if e.to_string().contains("is not symbolic") => {
        // recreate the symref via git symbolic-ref / git remote set-head, then retry
    }
    other => other?,
}

Prevention

When it happens

Trigger: The target of refs/remotes/{remote}/HEAD is not a symbolic name: the ref was written as a direct OID (by a tool or manual refs update), or ref storage returned a non-name target (e.g. a peeled object id).

Common situations: Third-party tools or scripts that rewrote remote HEAD as a detached ref; manually edited packed-refs; unusual repos migrated from other systems where HEAD symrefs were flattened.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18). Data as JSON: /api/errors/8a5b4832d128a246. Report an issue: GitHub.

Appendix: 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 58e5313667)