gitbutlerapp/gitbutler · error

Found only the GitButler managed block end marker. Refusing…

Error message

Found only the GitButler managed block end marker. Refusing to edit a partial managed block.

What it means

The mirror case of the partial-start-marker error: only the end marker of the GitButler managed block was found. Since a well-formed block requires start-then-end, the tool refuses to upsert to avoid corrupting surrounding user content.

Solutions

  1. Re-add the GitButler managed block start marker above the end marker.
  2. Or remove the orphaned end marker so a complete fresh block can be appended.
  3. Re-run the upsert once the markers are balanced.

Example fix

// before
...content...
<!-- GITBUTLER-MANAGED-BLOCK:END -->
// after
<!-- GITBUTLER-MANAGED-BLOCK:START -->
...content...
<!-- GITBUTLER-MANAGED-BLOCK:END -->
Defensive patterns

Strategy: validation

Validate before calling

const content = fs.readFileSync(file, 'utf8');
const startIdx = content.indexOf('GITBUTLER-MANAGED-BLOCK:START');
const endIdx = content.indexOf('GITBUTLER-MANAGED-BLOCK:END');
if (endIdx !== -1 && startIdx === -1) throw new Error(`Orphaned end marker in ${file}; re-add the start marker`);

Prevention

When it happens

Trigger: upsert_managed_block finds MANAGED_BLOCK_END with no preceding MANAGED_BLOCK_START — e.g. the user deleted the start marker line, or an automated edit truncated the block header.

Common situations: Manual cleanup of instruction files that removed the header comment; linters/formatters stripping the start marker line; merge conflict resolution keeping only the tail.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18). Data as JSON: /api/errors/15c2bbcb139beb11. Report an issue: GitHub.

Appendix: source

Thrown at crates/but/src/command/agent/files.rs:106

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()
    }
}

pub(super) fn upsert_managed_block(existing: &str, block: &str) -> Result<String> {
    let start = find_line_anchored(existing, MANAGED_BLOCK_START, 0);
    let end = find_line_anchored(existing, MANAGED_BLOCK_END, 0);

    match (start, end) {
        // No managed block yet: append a fresh one.
        (None, None) => return Ok(append_managed_block(existing, block)),
        (Some(_), None) => anyhow::bail!(
            "Found only the GitButler managed block start marker. Refusing to edit a partial managed block."
        ),
        (None, Some(_)) => anyhow::bail!(
            "Found only the GitButler managed block end marker. Refusing to edit a partial managed block."
        ),
        (Some(start), Some(end)) if end < start => anyhow::bail!(
            "Found GitButler managed block markers in the wrong order (end before start). Refusing to edit a malformed managed block."
        ),
        // Well-formed: a start marker with an end after it. Fall through to replace.
        (Some(_), Some(_)) => {}
    }

    // Replace the first block with the fresh one and drop any extra blocks
    // (e.g. left over from an earlier buggy run), so the file converges to
    // exactly one block.
    let mut spans = Vec::new();
    for span in managed_block_spans(existing)? {
        let mut block_end = span.end;
        if existing[block_end..].starts_with("\r\n") {
            block_end += 2;
        } else if existing[block_end..].starts_with('\n') {

View on GitHub (pinned to 58e5313667)