{"record":{"id":"ed7bd745c2b75f03","repo":"gitbutlerapp/gitbutler","slug":"visible-worktree-changed-since-the-editor-was-c","errorCode":null,"errorMessage":"Visible worktree {} changed since the editor was created: expected {} at {}, got {} at {}","messagePattern":"Visible worktree (.+?) changed since the editor was created: expected (.+?) at (.+?), got (.+?) at (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-rebase/src/graph_rebase/materialize.rs","lineNumber":97,"sourceCode":"    repo: &gix::Repository,\n    specs: Vec<LinkedCheckoutSpec>,\n) -> Result<Vec<LinkedCheckoutRepo>> {\n    if specs.is_empty() {\n        return Ok(Vec::new());\n    }\n    let proxies = repo.worktrees()?;\n    specs\n        .into_iter()\n        .map(|spec| {\n            let proxy = proxies\n                .iter()\n                .find(|proxy| proxy.id() == spec.name.as_bstr())\n                .with_context(|| format!(\"Visible worktree {} no longer exists\", spec.name))?;\n            let worktree_repo = proxy.clone().into_repo()?;\n            let actual_ref = worktree_repo.head_name()?;\n            let actual_head = worktree_repo.head_id()?.detach();\n            if actual_ref.as_ref() != spec.ref_name.as_ref() || actual_head != spec.initial_head {\n                bail!(\n                    \"Visible worktree {} changed since the editor was created: \\\n                     expected {} at {}, got {} at {}\",\n                    spec.name,\n                    spec.ref_name\n                        .as_ref()\n                        .map_or_else(|| \"detached\".into(), ToString::to_string),\n                    spec.initial_head,\n                    actual_ref\n                        .as_ref()\n                        .map_or_else(|| \"detached\".into(), ToString::to_string),\n                    actual_head\n                );\n            }\n            Ok(LinkedCheckoutRepo {\n                repo: worktree_repo,\n                target: spec.target,\n                merge_base_override: spec.merge_base_override,\n            })","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-rebase/src/graph_rebase/materialize.rs#L79-L115","documentation":"Before materializing a graph edit, the editor re-reads each linked worktree's HEAD and compares ref name and commit id against the values captured when the editor was created. Any drift (branch moved, different commit, attached vs detached) aborts with expected-vs-actual details, preventing the edit from clobbering concurrent changes.","triggerScenarios":"Materializing an edit after someone committed, checked out, rebased, or moved HEAD in a linked worktree while the editor session was open; or another process (git CLI, another app window) updated the ref.","commonSituations":"Long-lived interactive edit sessions; scripts or CI touching the same worktree concurrently; two windows of the app open on one repository.","solutions":["Re-create the editor (re-capture the worktree specs) and retry once the worktrees are idle","Finish or close other sessions/processes touching the worktree, then retry","Verify each worktree's current HEAD matches expectations before re-applying"],"exampleFix":"// before\neditor.materialize(...)?; // bails if any worktree HEAD drifted\n\n// after: re-verify specs first and rebuild on drift\nfor spec in &specs {\n    let (actual_ref, actual_head) = read_worktree_head(&repo, &spec.name)?;\n    if actual_ref.as_ref() != spec.ref_name.as_ref() || actual_head != spec.initial_head {\n        return rebuild_editor_and_retry();\n    }\n}\neditor.materialize(...)?;","handlingStrategy":"validation","validationCode":"// Rust: re-verify captured specs against live worktree HEADs before materializing\nfor spec in &specs {\n    let wt = worktree_repo(&repo, &spec.name)?;\n    let actual_ref = wt.head_name()?;\n    let actual_head = wt.head_id()?.detach();\n    if actual_ref.as_ref() != spec.ref_name.as_ref() || actual_head != spec.initial_head {\n        return recreate_editor(); // HEAD drifted since capture\n    }\n}","typeGuard":null,"tryCatchPattern":"On this error, do not force-apply: surface expected vs actual to the user, offer to recreate the editor from the current state, and retry once after the worktree is idle.","preventionTips":["Keep edit sessions short; materialize soon after capturing specs","Serialize access: discourage commits/checkouts in linked worktrees while an edit is pending"],"tags":["worktree","stale-state","optimistic-concurrency","graph-rebase"],"backgroundTag":"optimistic-concurrency-conflict","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}