gitbutlerapp/gitbutler · error

PR template exists but must be valid UTF-8 text or markdown

Error message

PR template exists but must be valid UTF-8 text or markdown

What it means

review_template_content() converts a fetched PR template FileInfo into a String. A file that exists (size is Some) whose bytes are not valid UTF-8 is rejected rather than lossily decoded, because template content is expected to be UTF-8 text or Markdown.

Source

Thrown at crates/but-api/src/legacy/forge.rs:81

    let forge_repo_info = but_forge::derive_forge_repo_info(&base_remote_url)
        .context("No forge could be determined for this repository branch")?;
    let forge_push_repo_info = if base_remote_url != push_remote_url {
        Some(
            but_forge::derive_forge_repo_info(&push_remote_url)
                .context("Failed to derive forge information for the push repository")?,
        )
    } else {
        None
    };
    Ok((forge_repo_info, forge_push_repo_info))
}

fn review_template_content(file: FileInfo) -> Result<String> {
    if file.size.is_none() {
        return Ok(String::new());
    }
    if !file.is_valid_utf8() {
        anyhow::bail!("PR template exists but must be valid UTF-8 text or markdown");
    }
    Ok(file.content.unwrap_or_default())
}

/// (Deprecated) Get the list of PR template paths for the given project and forge.
/// This function is deprecated in favor of `list_available_review_templates`.
#[but_api]
#[instrument(err(Debug))]
pub fn pr_templates(ctx: &but_ctx::Context, forge: ForgeName) -> Result<Vec<String>> {
    Ok(available_review_templates(&ctx.workdir_or_fail()?, &forge))
}

/// Get the forge provider name.
///
/// This is determined by the forge the base branch is pointing to.
/// Returns no value when the project has no target yet or its target forge is unknown.
#[but_api(napi)]
#[instrument(err(Debug))]

View on GitHub (pinned to 2497b8007a)

Solutions

  1. Re-save the template file as UTF-8 in the repository and push the fix
  2. Remove or rename the offending file so no template is detected
  3. Callers: catch the error and fall back to an empty template so review creation still works

Example fix

// caller-side graceful degradation
let template = match review_template_content(file) {
    Ok(text) => text,
    Err(err) if err.to_string().contains("must be valid UTF-8") => {
        warn!(?err, "ignoring non-UTF-8 PR template");
        String::new()
    }
    Err(err) => return Err(err),
};
Defensive patterns

Strategy: fallback

Validate before calling

if file.size.is_some() && !file.is_valid_utf8() {
    // skip the template (or warn) instead of letting template content fail
}

Type guard

fn template_is_usable(file: &FileInfo) -> bool {
    file.size.is_none() || file.is_valid_utf8()
}

Try / catch

match review_template_content(file) {
    Ok(content) => content,
    Err(err) if err.to_string().contains("must be valid UTF-8") => String::new(), // degrade gracefully
    Err(err) => return Err(err),
}

Prevention

When it happens

Trigger: The repository's pull request template (e.g. .github/PULL_REQUEST_TEMPLATE.md) is encoded as Latin-1/Windows-1252 or contains stray binary bytes, and a review/PR flow requests its content from the forge.

Common situations: Templates authored in Windows editors saving ANSI encoding; non-UTF-8 smart quotes or icons pasted in; a binary file accidentally named like a template.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@2497b8007a (2026-08-17). Data as JSON: /api/errors/e945aa82f7cb4315. Report an issue: GitHub.