From 62ab9e257bfd73eabf72b07e6b1b055b279f7acb Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 12 Feb 2026 15:36:31 +0000 Subject: [PATCH 1/2] fix: strip code fences and ANSI escapes from log analysis comments Co-Authored-By: john@hyprnote.com --- crates/api-support/src/github.rs | 6 ++++-- crates/api-support/src/logs.rs | 36 +++++++++++++++++++++++++++++++- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/crates/api-support/src/github.rs b/crates/api-support/src/github.rs index 06052164bc..8e8e5e0c7f 100644 --- a/crates/api-support/src/github.rs +++ b/crates/api-support/src/github.rs @@ -127,15 +127,17 @@ fn make_title(description: &str, fallback: &str) -> (String, String) { } async fn attach_log_analysis(state: &AppState, issue_number: u64, log_text: &str) { + let clean_logs = logs::strip_ansi_escapes(log_text); + let log_summary = - logs::analyze_logs(&state.config.openrouter.openrouter_api_key, log_text).await; + logs::analyze_logs(&state.config.openrouter.openrouter_api_key, &clean_logs).await; let summary_section = match log_summary.as_deref() { Some(s) if !s.trim().is_empty() => format!("### Summary\n```\n{s}\n```"), _ => "_No errors or warnings found._".to_string(), }; - let tail = logs::safe_tail(log_text, 10000); + let tail = logs::safe_tail(&clean_logs, 10000); let comment = LogAnalysisComment { summary_section: &summary_section, tail, diff --git a/crates/api-support/src/logs.rs b/crates/api-support/src/logs.rs index 0e7ce8fa69..036e831192 100644 --- a/crates/api-support/src/logs.rs +++ b/crates/api-support/src/logs.rs @@ -1,3 +1,37 @@ +fn strip_code_fences(s: &str) -> String { + let trimmed = s.trim(); + let stripped = trimmed + .strip_prefix("```") + .and_then(|s| { + let s = s.strip_prefix('\n').unwrap_or(s); + s.strip_suffix("```") + }) + .map(|s| s.trim()) + .unwrap_or(trimmed); + stripped.to_string() +} + +pub(crate) fn strip_ansi_escapes(s: &str) -> String { + let mut result = String::with_capacity(s.len()); + let mut chars = s.chars().peekable(); + while let Some(c) = chars.next() { + if c == '\x1b' { + if chars.peek() == Some(&'[') { + chars.next(); + while let Some(&next) = chars.peek() { + chars.next(); + if next.is_ascii_alphabetic() { + break; + } + } + } + } else { + result.push(c); + } + } + result +} + pub(crate) fn safe_tail(s: &str, max_bytes: usize) -> &str { let start = s.len().saturating_sub(max_bytes); let start = s @@ -27,5 +61,5 @@ pub(crate) async fn analyze_logs(api_key: &str, logs: &str) -> Option { let resp = client.chat_completion(&req).await.ok()?; let content = resp.choices.first()?.message.content.as_ref()?; let text = content.as_text()?; - Some(text.chars().take(800).collect()) + Some(strip_code_fences(text).chars().take(800).collect()) } From d84105f8c6ff436d2e56f9a2a4ddbd0ca3931585 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 12 Feb 2026 15:37:52 +0000 Subject: [PATCH 2/2] fix: disable HTML escaping in markdown templates Co-Authored-By: john@hyprnote.com --- crates/api-support/src/github.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/api-support/src/github.rs b/crates/api-support/src/github.rs index 8e8e5e0c7f..f5ac6a2f60 100644 --- a/crates/api-support/src/github.rs +++ b/crates/api-support/src/github.rs @@ -8,7 +8,7 @@ const GITHUB_OWNER: &str = "fastrepl"; const GITHUB_REPO: &str = "hyprnote"; #[derive(Template)] -#[template(path = "bug_report.md.jinja")] +#[template(path = "bug_report.md.jinja", escape = "none")] struct BugReportBody<'a> { description: &'a str, platform: &'a str, @@ -19,7 +19,7 @@ struct BugReportBody<'a> { } #[derive(Template)] -#[template(path = "feature_request.md.jinja")] +#[template(path = "feature_request.md.jinja", escape = "none")] struct FeatureRequestBody<'a> { description: &'a str, platform: &'a str, @@ -30,7 +30,7 @@ struct FeatureRequestBody<'a> { } #[derive(Template)] -#[template(path = "log_analysis.md.jinja")] +#[template(path = "log_analysis.md.jinja", escape = "none")] struct LogAnalysisComment<'a> { summary_section: &'a str, tail: &'a str,