affaan-m/ECC · warning

selected hunk is already staged

Error message

selected hunk is already staged

What it means

stage_hunk only stages hunks that live in the unstaged section of a diff view. If the caller passes a hunk whose section is Staged, the library refuses with this error because there is nothing to stage — the change is already in the index. It is a state precondition check, not a git failure.

Solutions

  1. Check hunk.section == GitPatchSectionKind::Unstaged before calling stage_hunk; disable the action otherwise.
  2. Refresh the patch view and use fresh hunk data, since section membership can change after any staging operation.
  3. Route staged hunks to unstage_hunk (or no-op) in the UI layer instead.

Example fix

// before: uniform handler
stage_hunk(&worktree, &hunk)?;

// after: guard by section
if hunk.section == GitPatchSectionKind::Unstaged {
    stage_hunk(&worktree, &hunk)?;
}
Defensive patterns

Strategy: type-guard

Validate before calling

if hunk.section != GitPatchSectionKind::Unstaged {
    return Err(anyhow!("hunk is already staged; nothing to stage"));
}

Type guard

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

Try / catch

match stage_hunk(&worktree, &hunk) {
    Ok(()) => refresh_patch_view(),
    Err(e) if e.to_string().contains("already staged") => refresh_patch_view(),
    Err(e) => eprintln!("stage hunk failed: {e:#}"),
}

Prevention

When it happens

Trigger: Calling stage_hunk(worktree, hunk) with a hunk obtained from the staged side of a patch view (hunk.section == GitPatchSectionKind::Staged), e.g. wiring a UI button to stage for both diff panels without checking section kind.

Common situations: Rendering one unified list of hunks and reusing the stage action for every row; stale UI state where the hunk was staged between render and click; copying a hunk from a staged-diff context into a stage call.

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 affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/7c941b3fac63ffd1. Report an issue: GitHub.

Appendix: source

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

            &unstaged_patch,
        ));
    }

    if sections.is_empty() {
        Ok(None)
    } else {
        Ok(Some(GitStatusPatchView {
            path: entry.path.clone(),
            display_path: entry.display_path.clone(),
            patch: sections.join("\n\n"),
            hunks,
        }))
    }
}

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",

View on GitHub (pinned to 8321021c54)