gitbutlerapp/gitbutler · error
Found a GitButler managed block start marker without a…
Error message
Found a GitButler managed block start marker without a matching end marker. Refusing to edit a partial managed block.
What it means
The managed-block editor scans a file for GitButler MANAGED_BLOCK_START/END marker pairs and records the spans it owns. If a start marker exists without a matching end marker, the block is partial/corrupt; editing it could truncate or duplicate user content, so the operation refuses.
Solutions
- Restore the missing end marker (MANAGED_BLOCK_END) so the block is complete, then retry
- If the block is unwanted, remove the entire start marker (and any orphaned body) instead of leaving a partial block
- Regenerate the file section from a known-good version (e.g. `git checkout -- <file>` if committed state was intact)
- Avoid hand-editing between the marker lines; edit outside the managed span
Example fix
// before: crashing on a partial block
upsert_managed_block(&mut content, "## Tasks", new_bullets)?;
// after: repair or reject the partial block first
if content.contains(MANAGED_BLOCK_START) && !content.contains(MANAGED_BLOCK_END) {
anyhow::bail!("repair the partial managed block (missing end marker) before updating");
}
upsert_managed_block(&mut content, "## Tasks", new_bullets)?; Defensive patterns
Strategy: validation
Validate before calling
fn has_complete_managed_block(content: &str) -> bool {
let starts = content.matches(MANAGED_BLOCK_START).count();
let ends = content.matches(MANAGED_BLOCK_END).count();
starts == 0 || starts == ends
} Try / catch
match upsert_managed_block(&mut content, heading, bullets) {
Err(e) if e.to_string().contains("without a matching end marker") => {
repair_or_regenerate_file_before_retry()?;
upsert_managed_block(&mut content, heading, bullets)
}
r => r,
} Prevention
- Never hand-edit between managed-block markers
- Restore the end marker immediately if a tool or editor removes it
- Keep these files in version control so partial writes can be reverted
- Validate marker pairing after any external tool rewrites the file
When it happens
Trigger: Calling upsert_managed_block or replace_retired_bullet on a file whose content contains an opening managed-block marker with no closing marker before EOF — typically because a previous write was truncated, a user manually deleted the end marker, or a formatter/tool stripped it.
Common situations: Manual editing of agent-maintained markdown where a user removed marker lines they thought were decoration; a crash/disk-full during a prior write leaving a truncated file; templating tools that rewrite the file and drop unknown comment lines; partially committed merges of these files.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- Found GitButler managed block markers in the wrong order…
- Found only the GitButler managed block end marker. Refusing…
- Found only the GitButler managed block start marker…
- a committed transaction always materializes a workspace
- Ambiguous branch ' ', matches multiple items
AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18).
Data as JSON: /api/errors/8eb123fee4659b95.
Report an issue: GitHub.
Appendix: source
Thrown at crates/but/src/command/agent/files.rs:75
}
open
}
/// Byte spans of each managed block, from the first byte of the start marker
/// to just past the last byte of the end marker (both markers lie inside the
/// end-exclusive `Range`). Pairs every line-anchored start with the first
/// line-anchored end after it. Errors when a start marker has no matching end,
/// so callers refuse to touch a malformed block.
pub(super) fn managed_block_spans(existing: &str) -> Result<Vec<std::ops::Range<usize>>> {
let mut spans = Vec::new();
let mut pos = 0;
while let Some(start) = find_line_anchored(existing, MANAGED_BLOCK_START, pos) {
let Some(end) = find_line_anchored(
existing,
MANAGED_BLOCK_END,
start + MANAGED_BLOCK_START.len(),
) else {
anyhow::bail!(
"Found a GitButler managed block start marker without a matching end marker. Refusing to edit a partial managed block."
);
};
let span_end = end + MANAGED_BLOCK_END.len();
spans.push(start..span_end);
pos = span_end;
}
Ok(spans)
}
/// Rewrite `block` to use CRLF line endings when `existing` already does, so a
/// replaced or appended block does not introduce mixed line endings.
fn match_line_endings(existing: &str, block: &str) -> String {
if existing.contains("\r\n") {
block.replace("\r\n", "\n").replace('\n', "\r\n")
} else {
block.to_string()
}View on GitHub (pinned to 58e5313667)