{"record":{"id":"4110b9aba1dee815","repo":"affaan-m/ECC","slug":"git-status-failed-stderr","errorCode":null,"errorMessage":"git status failed: {stderr}","messagePattern":"git status failed: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"ecc2/src/worktree/mod.rs","lineNumber":283,"sourceCode":"\n    if parts.is_empty() {\n        Ok(Some(format!(\"Clean relative to {}\", worktree.base_branch)))\n    } else {\n        Ok(Some(parts.join(\" | \")))\n    }\n}\n\npub fn git_status_entries(worktree: &WorktreeInfo) -> Result<Vec<GitStatusEntry>> {\n    let output = Command::new(\"git\")\n        .arg(\"-C\")\n        .arg(&worktree.path)\n        .args([\"status\", \"--porcelain=v1\", \"--untracked-files=all\"])\n        .output()\n        .context(\"Failed to load git status entries\")?;\n\n    if !output.status.success() {\n        let stderr = String::from_utf8_lossy(&output.stderr);\n        anyhow::bail!(\"git status failed: {stderr}\");\n    }\n\n    Ok(String::from_utf8_lossy(&output.stdout)\n        .lines()\n        .filter_map(parse_git_status_entry)\n        .collect())\n}\n\npub fn stage_path(worktree: &WorktreeInfo, path: &str) -> Result<()> {\n    let output = Command::new(\"git\")\n        .arg(\"-C\")\n        .arg(&worktree.path)\n        .args([\"add\", \"--\"])\n        .arg(path)\n        .output()\n        .with_context(|| format!(\"Failed to stage {}\", path))?;\n    if output.status.success() {\n        Ok(())","sourceCodeStart":265,"sourceCodeEnd":301,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/ecc2/src/worktree/mod.rs#L265-L301","documentation":"git_status_entries shells out to `git -C <worktree.path> status --porcelain=v1 --untracked-files=all` and re-throws git's stderr when the process exits non-zero. It is the foundation for nearly every status-driven operation (staging, commit readiness, health), so a failure here cascades upstream. The error message is git's own stderr, so the underlying cause is usually visible in the message text.","triggerScenarios":"Calling git_status_entries, has_staged_changes, health, or diff_file_preview on a WorktreeInfo whose path no longer exists, is not inside a git repository, has a stale `.git/index.lock`, or is unreadable by the current process. Also triggered when the worktree was pruned/deleted out of band while a session still holds a stale WorktreeInfo handle.","commonSituations":"Worktree removed manually (rm -rf) or via `git worktree prune` while the session keeps an old WorktreeInfo; a previous git process crashed and left `.git/index.lock`; CI/container runs as a different uid than the worktree owner; the worktree path was a symlink whose target moved.","solutions":["Check that `worktree.path` is still an existing directory before calling (e.g. `if !worktree.path.is_dir() { ... }`).","Remove a stale `.git/index.lock` if present: `rm -f <repo>/.git/index.lock` after confirming no git process is running.","Re-resolve the WorktreeInfo via create_for_session / the session store if the worktree was recreated or its path changed.","Verify filesystem permissions and that the path is inside a repo (`git -C <path> rev-parse --is-inside-work-tree`)."],"exampleFix":"// before\nlet entries = git_status_entries(&worktree)?;\n\n// after\nif !worktree.path.is_dir() {\n    anyhow::bail!(\"worktree path missing: {}\", worktree.path.display());\n}\nlet entries = git_status_entries(&worktree)?;","handlingStrategy":"validation","validationCode":"use std::path::Path;\n\nfn ensure_worktree_usable(path: &Path) -> anyhow::Result<()> {\n    if !path.is_dir() {\n        anyhow::bail!(\"worktree path is not a directory: {}\", path.display());\n    }\n    let lock = path.join(\".git\").join(\"index.lock\");\n    // Also handle a gitdir file for linked worktrees\n    if lock.exists() {\n        anyhow::bail!(\"stale index.lock present: {}\", lock.display());\n    }\n    Ok(())\n}\n\n// call before git_status_entries\nensure_worktree_usable(&worktree.path)?;\nlet entries = git_status_entries(&worktree)?;","typeGuard":null,"tryCatchPattern":"match git_status_entries(&worktree) {\n    Ok(entries) => { /* proceed */ }\n    Err(e) => {\n        // Distinguish spawn-failure (.context) from git-failure (bail!).\n        if format!(\"{e:#}\").contains(\"git status failed\") {\n            // surface to user as 'repo/worktree unusable'\n        }\n        return Err(e);\n    }\n}","preventionTips":["Never delete worktrees manually; always go through the worktree manager so WorktreeInfo handles stay valid.","Hold at most one concurrent git process per worktree to avoid index.lock races.","Refresh WorktreeInfo from the session store at the start of each user action.","Run the app as the user that owns the worktree to avoid permission denials."],"tags":["git","subprocess","worktree","filesystem","status"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}