{"record":{"id":"3f3e6b5d1a8dadc1","repo":"gitbutlerapp/gitbutler","slug":"target-oid-must-exist-when-ahead-calculation-is-en","errorCode":null,"errorMessage":"target OID must exist when ahead calculation is enabled","messagePattern":"target OID must exist when ahead calculation is enabled","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"crates/but/src/command/legacy/branch/list.rs","lineNumber":194,"sourceCode":"                // in the target and has no commits ahead to show.\n                repo.merge_base_with_graph(branch.head, target_oid, &mut graph)\n                    .map(|merge_base| merge_base.detach() != branch.head)\n                    .unwrap_or(true)\n            })\n            .take(num_branches_to_take)\n            .collect()\n    } else {\n        branches.into_iter().take(num_branches_to_take).collect()\n    };\n\n    let has_more_branches = branches_to_show.len() > max_branches;\n    let branches_to_show: Vec<_> = branches_to_show.into_iter().take(max_branches).collect();\n\n    // Calculate commits ahead if requested\n    let commits_ahead_map: Option<HashMap<String, usize>> = if ahead {\n        Some(calculate_commits_ahead(\n            ctx,\n            target_oid.expect(\"target OID must exist when ahead calculation is enabled\"),\n            &branches_to_show,\n        )?)\n    } else {\n        None\n    };\n\n    // Check merge status if requested\n    let merge_status_map: Option<HashMap<String, bool>> = if check_merge {\n        Some(check_branches_merge_cleanly(\n            ctx,\n            target_oid.expect(\"target OID must exist when merge check is enabled\"),\n            &applied_stacks,\n            &branches_to_show,\n        )?)\n    } else {\n        None\n    };\n","sourceCodeStart":176,"sourceCodeEnd":212,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/2497b8007aa4a1922dae9a805b32ffe5b5037785/crates/but/src/command/legacy/branch/list.rs#L176-L212","documentation":"Defensive expect in 'but branch list': when commits-ahead counting is enabled (the default; disabled with --no-ahead), the workspace target's commit id must be present. Upstream, target_oid is resolved exactly when '!empty || ahead || check_merge' (list.rs:47-55), and ResolvedTarget::oid() returns a plain ObjectId - resolution either succeeds or propagates its error via '?'. With the current wiring the expect cannot fire; it guards against future flag-wiring desync. When the target genuinely cannot be resolved (workspace without a base branch), users see the propagated resolution error instead of this panic.","triggerScenarios":"Only a refactor that resolves target_oid under a condition no longer implied by 'ahead' - for example adding a new mode that turns on ahead counting without updating the resolution condition at list.rs:47.","commonSituations":"Contributors adding flags that need (or deliberately skip) target resolution; not reachable by end users through valid CLI input.","solutions":["Keep the resolution condition at list.rs:47 in sync with every flag that consumes target_oid","Maintainer: replace the expects with ok_or_else errors naming the flag that required the target","Add a CLI test matrix over --no-ahead, --no-check and --empty combinations"],"exampleFix":"// before\ntarget_oid.expect(\"target OID must exist when ahead calculation is enabled\")\n\n// after\nlet target_oid = target_oid.ok_or_else(|| {\n    anyhow::anyhow!(\"--ahead needs a resolvable workspace target branch; run fetch/sync first\")\n})?;","handlingStrategy":"validation","validationCode":"// Lock the wiring: target resolution must cover every flag that needs the target\n#[test]\nfn branch_list_flags_never_panic_on_target() {\n    // exercises default (ahead+check on), --no-ahead, --no-check, --empty combos\n    for args in [[\"branch\", \"list\"], [\"branch\", \"list\", \"--no-ahead\"], [\"branch\", \"list\", \"--no-check\", \"--empty\"]] {\n        env.but(&args).assert().success(); // or expected failure - but never a panic\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Whenever adding a flag that consumes target_oid, extend the resolution condition in the same commit","Prefer propagating a descriptive error (ok_or_else) over expects for Option<OID> plumbing in CLI paths","When users report 'target' failures, look for the propagated resolution error - the expect itself is a wiring guard"],"tags":["rust","panic","invariant","cli","target-branch","branch-list"],"backgroundTag":"missing-target-ref","analyzedSha":"2497b8007aa4a1922dae9a805b32ffe5b5037785","analyzedAt":"2026-08-17T00:30:25.648Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}