{"record":{"id":"48b3b4530ab2940f","repo":"affaan-m/ECC","slug":"no-staged-changes-to-commit","errorCode":null,"errorMessage":"no staged changes to commit","messagePattern":"no staged changes to commit","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"warning","filePath":"ecc2/src/worktree/mod.rs","lineNumber":461,"sourceCode":"                );\n            }\n            git_apply_patch(\n                &worktree.path,\n                &[\"-R\", \"--index\"],\n                &hunk.patch,\n                \"reset selected staged hunk\",\n            )\n        }\n    }\n}\n\npub fn commit_staged(worktree: &WorktreeInfo, message: &str) -> Result<String> {\n    let message = message.trim();\n    if message.is_empty() {\n        anyhow::bail!(\"commit message cannot be empty\");\n    }\n    if !has_staged_changes(worktree)? {\n        anyhow::bail!(\"no staged changes to commit\");\n    }\n\n    let output = Command::new(\"git\")\n        .arg(\"-C\")\n        .arg(&worktree.path)\n        .args([\"commit\", \"-m\", message])\n        .output()\n        .context(\"Failed to create commit\")?;\n    if !output.status.success() {\n        let stderr = String::from_utf8_lossy(&output.stderr);\n        anyhow::bail!(\"git commit failed: {stderr}\");\n    }\n\n    let rev_parse = Command::new(\"git\")\n        .arg(\"-C\")\n        .arg(&worktree.path)\n        .args([\"rev-parse\", \"--short\", \"HEAD\"])\n        .output()","sourceCodeStart":443,"sourceCodeEnd":479,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/ecc2/src/worktree/mod.rs#L443-L479","documentation":"commit_staged calls has_staged_changes (which scans `git_status_entries` for any entry with `staged == true`) and bails if none exist. This prevents `git commit` from failing or producing an empty commit. It is a state precondition, not a git subprocess failure.","triggerScenarios":"User hits commit after unstaging everything; staged changes were already committed by another path; status was stale (the index changed underneath the UI) so the caller believed something was staged when nothing was.","commonSituations":"Double-commit attempt (UI did not refresh after the previous commit); race where a background task reset the index; user unstaged the last path but the commit button stayed enabled.","solutions":["Call `git_status_entries` immediately before commit and disable the commit affordance unless at least one entry has `staged == true`.","Stage explicitly via stage_path/stage_hunk before retrying.","Refresh status after any staging mutation and re-check before commit."],"exampleFix":"// before\nlet hash = commit_staged(&worktree, msg)?;\n\n// after\nif !has_staged_changes(&worktree)? {\n    return Err(anyhow!(\"nothing staged; stage changes first\"));\n}\nlet hash = commit_staged(&worktree, msg)?;","handlingStrategy":"validation","validationCode":"use crate::worktree::has_staged_changes;\n\nif !has_staged_changes(&worktree)? {\n    return Err(anyhow!(\"nothing staged to commit; stage at least one path first\"));\n}\nlet hash = commit_staged(&worktree, msg)?;","typeGuard":"fn has_any_staged(entries: &[GitStatusEntry]) -> bool {\n    entries.iter().any(|e| e.staged)\n}","tryCatchPattern":"match commit_staged(&worktree, msg) {\n    Ok(hash) => Ok(hash),\n    Err(e) if format!(\"{e}\").contains(\"no staged changes\") => {\n        // surface 'stage changes first' and abort\n        Err(e)\n    }\n    Err(e) => Err(e),\n}","preventionTips":["Refresh `git_status_entries` right before enabling the commit button and gate it on `entry.staged`.","After every commit, refresh status so the button does not stay enabled on stale state.","Avoid concurrent commit paths in the same worktree."],"tags":["git","worktree","validation","commit","index"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}