gitbutlerapp/gitbutler · error
candidate turns always have review or branch evidence
Error message
candidate turns always have review or branch evidence
What it means
`evidence_tier` in but-agentlog's projection maps `(has_review, has_branch)` to a `ProjectionEvidenceTier`; the `(false, false)` arm is `unreachable!` because upstream candidate collection only keeps turns that already carry review or branch evidence. The panic therefore signals that the candidate filter upstream of this function was bypassed or regressed, letting evidence-free turns through.
Source
Thrown at crates/but-agentlog/src/projection.rs:544
if has_review {
reasons.push(ProjectionMatchReason {
kind: ProjectionMatchKind::ReviewTarget,
});
}
if has_branch {
reasons.push(ProjectionMatchReason {
kind: ProjectionMatchKind::BranchTarget,
});
}
reasons
}
fn evidence_tier(has_review: bool, has_branch: bool) -> ProjectionEvidenceTier {
match (has_review, has_branch) {
(true, true) => ProjectionEvidenceTier::Supporting,
(true, false) => ProjectionEvidenceTier::Direct,
(false, true) => ProjectionEvidenceTier::Possible,
(false, false) => unreachable!("candidate turns always have review or branch evidence"),
}
}
fn evidence_rank(tier: ProjectionEvidenceTier) -> usize {
match tier {
ProjectionEvidenceTier::Supporting => 0,
ProjectionEvidenceTier::Direct => 1,
ProjectionEvidenceTier::Possible => 2,
}
}
fn project_records(
request: &ProjectionRequest,
session_key: &str,
turn_key: &str,
records: SessionRecords,
saw_truncated_text: &mut bool,
) -> Vec<ProjectionRecord> {View on GitHub (pinned to caf1f223d3)
Solutions
- Filter candidates to `has_review || has_branch` before computing tiers.
- If new legitimate turn kinds can be evidence-free, change `evidence_tier` to return `Option<ProjectionEvidenceTier>` and skip them instead of panicking.
- Fix fixtures so candidate turns include at least one evidence kind.
Example fix
// before
fn evidence_tier(has_review: bool, has_branch: bool) -> ProjectionEvidenceTier {
match (has_review, has_branch) {
// ...
(false, false) => unreachable!("candidate turns always have review or branch evidence"),
}
}
// after
fn evidence_tier(has_review: bool, has_branch: bool) -> Option<ProjectionEvidenceTier> {
match (has_review, has_branch) {
(true, true) => Some(ProjectionEvidenceTier::Supporting),
(true, false) => Some(ProjectionEvidenceTier::Direct),
(false, true) => Some(ProjectionEvidenceTier::Possible),
(false, false) => None, // not a candidate; skip
}
} Defensive patterns
Strategy: type-guard
Validate before calling
// only feed turns that carry evidence into tier computation
let candidates: Vec<_> = turns
.into_iter()
.filter(|t| t.review_id.is_some() || t.branch_name.is_some())
.collect();
for turn in candidates {
let tier = evidence_tier(turn.review_id.is_some(), turn.branch_name.is_some());
// ...
} Type guard
fn evidence_tier_safe(has_review: bool, has_branch: bool) -> Option<ProjectionEvidenceTier> {
match (has_review, has_branch) {
(true, true) => Some(ProjectionEvidenceTier::Supporting),
(true, false) => Some(ProjectionEvidenceTier::Direct),
(false, true) => Some(ProjectionEvidenceTier::Possible),
(false, false) => None, // not a candidate
}
} Prevention
- Keep the upstream candidate filter (review OR branch evidence) and the tier function in the same module review scope.
- Prefer Option-returning tier functions so new turn kinds degrade to 'skipped', not a panic.
- Include evidence-free turns in fixtures to assert they are filtered before tiering.
When it happens
Trigger: A code path builds a candidate-turn list without the evidence filter and feeds it to `evidence_tier`; the upstream filter's condition is widened during a refactor; test fixtures synthesize turns with no review and no branch attached.
Common situations: Extending but-agentlog projections with new candidate sources; loosening the candidate query during debugging; fixture data that omits the evidence fields.
Related errors
- BUG: {id} is queued
- validated AI responses only produce content picks
- infinite loop should find a unique name
- we never return these as the status iteration is configured
- BUG: we use 'matching' so there are no directories
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/a589c2b73bac9c78.
Report an issue: GitHub.