GitoxideLabs/gitoxide · error

changes can only be stashed at the current HEAD

Error message

changes can only be stashed at the current HEAD

What it means

Manual stash saving keys the saved worktree state by the current HEAD commit ID. This error fires when the caller supplied a commit ID that does not equal the repository's current HEAD id, so tix cannot attribute the state to HEAD. It guards against stashing changes that belong to a different, unborn, or non-checked-out commit.

Solutions

  1. Pass the commit id obtained from the repository's current `head_id()` at the time of the call
  2. Re-checkout or reset HEAD so it matches the id you intend to stash at
  3. Re-read HEAD and retry the save atomically if HEAD may have moved

Example fix

// before
let target = old_commit_id;
stash::save_manual(path, bare, workdir, target)?;
// after
let repo = gix::open(path)?;
let target = repo.head_id().expect("born HEAD").detach();
stash::save_manual(path, bare, workdir, target)?;
Defensive patterns

Strategy: validation

Validate before calling

let repo = gix::open(path)?;
let head = repo.head_id().context("born HEAD required")?.detach();
if head != id {
    anyhow::bail!("re-read HEAD; the id to stash must equal the current HEAD");
}

Prevention

When it happens

Trigger: Calling `stash::save_manual` with an `id` argument different from `repo.head_id()` — e.g. after HEAD moved (checkout/rebase) between obtaining the id and saving, or passing an arbitrary commit id.

Common situations: Scripting stashes while HEAD changes concurrently; passing a target commit id from a UI that is not the checked-out HEAD; detached HEAD transitions mid-operation.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of GitoxideLabs/gitoxide@e73179060b (2026-09-08). Data as JSON: /api/errors/9ef70d453cc78f5e. Report an issue: GitHub.

Appendix: source

Thrown at gix-tix/src/edit/stash.rs:130

}

fn delete_edit(name: gix::refs::FullName, target: Target) -> RefEdit {
    RefEdit::delete(name, PreviousValue::MustExistAndMatch(target))
}

#[tracing::instrument(skip_all, fields(commit_id = %id))]
pub(crate) fn save_manual(repository_path: &Path, bare: bool, id: ObjectId) -> Result<String> {
    let repo = open_repository(repository_path, bare, false).context("could not open repository to stash changes")?;
    let workdir = repo
        .workdir()
        .context("stashing changes requires a worktree")?
        .to_owned();
    let head = repo
        .head_id()
        .context("stashing changes requires a born HEAD")?
        .detach();
    if head != id {
        anyhow::bail!("changes can only be stashed at the current HEAD");
    }
    if repo
        .index_or_empty()
        .context("could not inspect the index before stashing")?
        .entries()
        .iter()
        .any(|entry| entry.stage() != gix::index::entry::Stage::Unconflicted)
    {
        anyhow::bail!("cannot stash changes with unresolved index conflicts");
    }
    let name = reference(id)?;
    if repo.try_find_reference(name.as_ref())?.is_some() {
        anyhow::bail!("{} already has saved worktree state", id.to_hex_with_len(7));
    }
    drop(repo);
    if !super::review::is_dirty(&workdir)? {
        anyhow::bail!("there are no worktree or index changes to stash");
    }

View on GitHub (pinned to e73179060b)