gitbutlerapp/gitbutler · warning
but_meta::Stack::derived_name: Stack is uninitialized
Error message
but_meta::Stack::derived_name: Stack is uninitialized
What it means
Returned by Stack::derived_name in crates/but-meta/src/virtual_branches_legacy_types.rs when the legacy Stack has an empty heads vector — derived_name takes the name of the top-most head (heads.last()), and with no heads there is no name to derive, so the stack is treated as uninitialized.
Source
Thrown at crates/but-meta/src/virtual_branches_legacy_types.rs:97
)]
#[serde(default)]
pub updated_timestamp_ms: u128,
#[deprecated(note = "Legacy field, do not use. Kept for backwards compatibility.")]
#[serde(default)]
pub name: String,
#[deprecated(note = "Legacy field, do not use. Kept for backwards compatibility.")]
#[serde(with = "but_serde::object_id")]
#[serde(default = "default_null_object_id")]
pub head: gix::ObjectId,
}
impl Stack {
/// This is the name of the top-most branch, provided by the API for convenience.
pub fn derived_name(&self) -> anyhow::Result<String> {
self.heads
.last()
.map(|head| head.name.clone())
.ok_or_else(|| anyhow!("but_meta::Stack::derived_name: Stack is uninitialized"))
}
}
fn default_null_object_id() -> gix::ObjectId {
gix::hash::Kind::Sha1.null()
}
fn default_true() -> bool {
true
}
fn default_false() -> bool {
false
}
fn serialize_u128<S>(x: &u128, s: S) -> anyhow::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{View on GitHub (pinned to caf1f223d3)
Solutions
- Guard callers: skip stacks whose heads are empty instead of calling derived_name on them.
- Filter during migration so only initialized stacks (heads non-empty) are converted.
- If every stack is empty, the source metadata is malformed — restore from backup or reinitialize.
Example fix
// before
let name = stack.derived_name()?;
// after
let name = stack
.heads
.last()
.map(|head| head.name.clone())
.unwrap_or_else(|| "(uninitialized)".to_string()); Defensive patterns
Strategy: type-guard
Validate before calling
// Only derive names for initialized stacks
for stack in stacks {
if stack.heads.is_empty() {
continue; // skip uninitialized legacy stack
}
let name = stack.derived_name()?;
} Type guard
fn stack_is_initialized(stack: &Stack) -> bool {
!stack.heads.is_empty()
} Try / catch
let name = match stack.derived_name() {
Ok(n) => n,
Err(e) if e.to_string().contains("uninitialized") => {
tracing::warn!("skipping uninitialized stack {:?}", stack); continue;
},
Err(e) => return Err(e),
}; Prevention
- Filter stacks by !heads.is_empty() before any derived_name() call in migration code.
- Treat zero-head stacks as data to be skipped or reported, not hard failures.
- Unit-test parsers against empty-head fixtures so this path is covered.
When it happens
Trigger: Deserializing a legacy stack entry that lists no heads (serde default empty vec) and then calling derived_name() on it; stacks created but never assigned branches in old data; migration code calling derived_name over all stacks without filtering.
Common situations: Processing imported or hand-written legacy metadata containing zero-head stacks; a partially written stack entry from an old crash; iterating stacks from an older format where heads were stored separately and the link was lost.
Related errors
- Missing stack '{stack_id}' for head '{name}'
- ownership ranges cannot be empty
- No uncommitted changes assigned to branch: {branch_name}
- Unable to determine target commit for unassigned change: {}
- Cannot unapply anonymous segments yet even if they have a st
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/fde06ee8cf3dc69a.
Report an issue: GitHub.