affaan-m/ECC · warning · anyhow::Error

selected hunk is not staged

Error message

selected hunk is not staged

What it means

unstage_hunk rejects any hunk whose `section` is not `GitPatchSectionKind::Staged`. Symmetric to the stage_hunk guard: you cannot unstage a hunk that is not currently staged. The function then applies the patch in reverse against `--cached`, which only makes sense for staged content.

Source

Thrown at ecc2/src/worktree/mod.rs:416

        }))
    }
}

pub fn stage_hunk(worktree: &WorktreeInfo, hunk: &GitPatchHunk) -> Result<()> {
    if hunk.section != GitPatchSectionKind::Unstaged {
        anyhow::bail!("selected hunk is already staged");
    }
    git_apply_patch(
        &worktree.path,
        &["--cached"],
        &hunk.patch,
        "stage selected hunk",
    )
}

pub fn unstage_hunk(worktree: &WorktreeInfo, hunk: &GitPatchHunk) -> Result<()> {
    if hunk.section != GitPatchSectionKind::Staged {
        anyhow::bail!("selected hunk is not staged");
    }
    git_apply_patch(
        &worktree.path,
        &["-R", "--cached"],
        &hunk.patch,
        "unstage selected hunk",
    )
}

pub fn reset_hunk(
    worktree: &WorktreeInfo,
    entry: &GitStatusEntry,
    hunk: &GitPatchHunk,
) -> Result<()> {
    if entry.untracked {
        anyhow::bail!("cannot reset hunks for untracked files");
    }

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Gate the call: only invoke unstage_hunk when `hunk.section == GitPatchSectionKind::Staged`.
  2. Refresh the patch view before dispatching the action.
  3. Disable the 'unstage' affordance when the selected hunk is in the Unstaged section.

Example fix

// before
unstage_hunk(&worktree, &hunk)?;

// after
match hunk.section {
    GitPatchSectionKind::Staged => unstage_hunk(&worktree, &hunk)?,
    GitPatchSectionKind::Unstaged => { /* not staged; no-op */ }
}
Defensive patterns

Strategy: validation

Validate before calling

use crate::worktree::{GitPatchHunk, GitPatchSectionKind, unstage_hunk};

fn try_unstage_hunk(worktree: &WorktreeInfo, hunk: &GitPatchHunk) -> anyhow::Result<()> {
    if hunk.section != GitPatchSectionKind::Staged {
        return Ok(()); // not staged — nothing to unstage
    }
    unstage_hunk(worktree, hunk)
}

Type guard

fn is_staged(hunk: &GitPatchHunk) -> bool {
    matches!(hunk.section, GitPatchSectionKind::Staged)
}

Try / catch

match unstage_hunk(&worktree, &hunk) {
    Ok(()) => { /* refresh */ }
    Err(e) if format!("{e}").contains("not staged") => { /* benign no-op */ }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Passing a `GitPatchHunk` from the `--- Working tree diff ---` (Unstaged) section into unstage_hunk. UI reuses the same handler for both sections without checking `hunk.section`.

Common situations: Keyboard shortcut for 'unstage' active while cursor sits on an unstaged hunk; hunk reference held across a status refresh that moved it into Unstaged.

Related errors


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/6d76c81d26dc0ffd. Report an issue: GitHub.