From 90c80bcf1f5ea202c2b4e22b5b9430c7a96708a9 Mon Sep 17 00:00:00 2001 From: Vitor Castro Date: Mon, 31 Aug 2026 00:29:04 +0100 Subject: [PATCH] fix: prevent ArgumentOutOfRange on agentic string truncation. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Short session ids and low observation budgets could throw during compaction, tool previews, MCP descriptions, and wiki compile — returning HTTP 400 to all apps. --- src/ContextMemory.Core/Agentic/AgentContextCompactor.cs | 6 +++++- .../Agentic/Prompts/AgenticToolObservationFormatter.cs | 4 +++- src/ContextMemory.Core/Agentic/SessionDiscoveryTools.cs | 3 ++- src/ContextMemory.Core/Session/SessionWikiCompiler.cs | 3 ++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/ContextMemory.Core/Agentic/AgentContextCompactor.cs b/src/ContextMemory.Core/Agentic/AgentContextCompactor.cs index bec3df8..172834e 100644 --- a/src/ContextMemory.Core/Agentic/AgentContextCompactor.cs +++ b/src/ContextMemory.Core/Agentic/AgentContextCompactor.cs @@ -66,7 +66,11 @@ public AgentContextCompactor( if (estimated <= maxTokens || messages.Count < 4) return null; - var historyId = $"history:{sessionId}:{iteration}:{Guid.NewGuid():N}"[..64]; + // Keep under common FS/DB id limits without assuming the prefix is already >= 64 chars + // (short session ids like Jira keys produced ArgumentOutOfRange on ..[64]). + var historyId = $"history:{sessionId}:{iteration}:{Guid.NewGuid():N}"; + if (historyId.Length > 64) + historyId = historyId[..64]; var transcript = SerializeTranscript(messages); try diff --git a/src/ContextMemory.Core/Agentic/Prompts/AgenticToolObservationFormatter.cs b/src/ContextMemory.Core/Agentic/Prompts/AgenticToolObservationFormatter.cs index 92529ce..6f50ccb 100644 --- a/src/ContextMemory.Core/Agentic/Prompts/AgenticToolObservationFormatter.cs +++ b/src/ContextMemory.Core/Agentic/Prompts/AgenticToolObservationFormatter.cs @@ -57,7 +57,9 @@ internal static string TruncateWithPointer( return string.IsNullOrWhiteSpace(artifactId) ? payload : payload + pointer; } - var previewLen = Math.Max(64, Math.Min(maxChars - 120, maxChars)); + // previewLen must never exceed payload.Length (maxChars can be < 64 + pointer overhead). + var previewLen = Math.Clamp(maxChars - 120, 1, payload.Length); + previewLen = Math.Min(previewLen, Math.Max(1, payload.Length - 1)); var preview = payload[..previewLen]; return preview + $"\n\n…[truncated {payload.Length - previewLen} chars; artifactId={id} " diff --git a/src/ContextMemory.Core/Agentic/SessionDiscoveryTools.cs b/src/ContextMemory.Core/Agentic/SessionDiscoveryTools.cs index 11a0a6e..30a3d2f 100644 --- a/src/ContextMemory.Core/Agentic/SessionDiscoveryTools.cs +++ b/src/ContextMemory.Core/Agentic/SessionDiscoveryTools.cs @@ -188,6 +188,7 @@ public static string ShortenDescription(string? description, int maxChars = 120) if (trimmed.Length <= maxChars) return trimmed + " (tool_describe for full schema)"; - return trimmed[..Math.Max(40, maxChars - 32)].TrimEnd() + "… (tool_describe for full schema)"; + var keep = Math.Clamp(maxChars - 32, 1, trimmed.Length); + return trimmed[..keep].TrimEnd() + "… (tool_describe for full schema)"; } } diff --git a/src/ContextMemory.Core/Session/SessionWikiCompiler.cs b/src/ContextMemory.Core/Session/SessionWikiCompiler.cs index 48abebe..70bfb2d 100644 --- a/src/ContextMemory.Core/Session/SessionWikiCompiler.cs +++ b/src/ContextMemory.Core/Session/SessionWikiCompiler.cs @@ -123,7 +123,8 @@ public static WikiCompileResult Compile( if (sb.Length > 0) sb.Append("\n\n"); sb.Append(header); - sb.Append(body[..availableForBody].TrimEnd()); + var take = Math.Min(availableForBody, body.Length); + sb.Append(body[..take].TrimEnd()); sb.Append(PageTruncatedSuffix); remaining = 0; included++;