{"record":{"id":"8eb123fee4659b95","repo":"gitbutlerapp/gitbutler","slug":"found-a-gitbutler-managed-block-start-marker-without-a","errorCode":null,"errorMessage":"Found a GitButler managed block start marker without a matching end marker. Refusing to edit a partial managed block.","messagePattern":"Found a GitButler managed block start marker without a matching end marker\\. Refusing to edit a partial managed block\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but/src/command/agent/files.rs","lineNumber":75,"sourceCode":"    }\n    open\n}\n\n/// Byte spans of each managed block, from the first byte of the start marker\n/// to just past the last byte of the end marker (both markers lie inside the\n/// end-exclusive `Range`). Pairs every line-anchored start with the first\n/// line-anchored end after it. Errors when a start marker has no matching end,\n/// so callers refuse to touch a malformed block.\npub(super) fn managed_block_spans(existing: &str) -> Result<Vec<std::ops::Range<usize>>> {\n    let mut spans = Vec::new();\n    let mut pos = 0;\n    while let Some(start) = find_line_anchored(existing, MANAGED_BLOCK_START, pos) {\n        let Some(end) = find_line_anchored(\n            existing,\n            MANAGED_BLOCK_END,\n            start + MANAGED_BLOCK_START.len(),\n        ) else {\n            anyhow::bail!(\n                \"Found a GitButler managed block start marker without a matching end marker. Refusing to edit a partial managed block.\"\n            );\n        };\n        let span_end = end + MANAGED_BLOCK_END.len();\n        spans.push(start..span_end);\n        pos = span_end;\n    }\n    Ok(spans)\n}\n\n/// Rewrite `block` to use CRLF line endings when `existing` already does, so a\n/// replaced or appended block does not introduce mixed line endings.\nfn match_line_endings(existing: &str, block: &str) -> String {\n    if existing.contains(\"\\r\\n\") {\n        block.replace(\"\\r\\n\", \"\\n\").replace('\\n', \"\\r\\n\")\n    } else {\n        block.to_string()\n    }","sourceCodeStart":57,"sourceCodeEnd":93,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/58e5313667b857ef39a730e380af31816a7b1768/crates/but/src/command/agent/files.rs#L57-L93","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before: crashing on a partial block\nupsert_managed_block(&mut content, \"## Tasks\", new_bullets)?;\n// after: repair or reject the partial block first\nif content.contains(MANAGED_BLOCK_START) && !content.contains(MANAGED_BLOCK_END) {\n    anyhow::bail!(\"repair the partial managed block (missing end marker) before updating\");\n}\nupsert_managed_block(&mut content, \"## Tasks\", new_bullets)?;","handlingStrategy":"validation","validationCode":"fn has_complete_managed_block(content: &str) -> bool {\n    let starts = content.matches(MANAGED_BLOCK_START).count();\n    let ends = content.matches(MANAGED_BLOCK_END).count();\n    starts == 0 || starts == ends\n}","typeGuard":null,"tryCatchPattern":"match upsert_managed_block(&mut content, heading, bullets) {\n    Err(e) if e.to_string().contains(\"without a matching end marker\") => {\n        repair_or_regenerate_file_before_retry()?;\n        upsert_managed_block(&mut content, heading, bullets)\n    }\n    r => r,\n}","preventionTips":["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"],"tags":["gitbutler","managed-block","file-corruption","markers"],"backgroundTag":"schema-validation-failed","analyzedSha":"58e5313667b857ef39a730e380af31816a7b1768","analyzedAt":"2026-09-18T06:50:32.052Z","contentChangedAt":"2026-09-18T06:50:32.052Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}