From 479fc7450e7c3e422b2fc1aef665d825073425df Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Wed, 28 Jan 2026 15:15:36 +0100 Subject: [PATCH] macOS: fix voice wake crash in trimmedAfterTrigger Fixed an index out of bounds crash in VoiceWakeRuntime.trimmedAfterTrigger that occurred when processing voice transcripts. The issue was caused by attempting to subscript a string with an index that could exceed the string's endIndex when using indices from a lowercased version of the string. Added a guard statement to check that the index is within bounds before attempting to subscript the string. If the index is out of bounds, the function continues to the next trigger instead of crashing. Fixes the crash reported in crash.txt at line 743. --- apps/macos/Sources/Moltbot/VoiceWakeRuntime.swift | 2 ++ .../MoltbotIPCTests/VoiceWakeRuntimeTests.swift | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/apps/macos/Sources/Moltbot/VoiceWakeRuntime.swift b/apps/macos/Sources/Moltbot/VoiceWakeRuntime.swift index 805211122..b4049a0a9 100644 --- a/apps/macos/Sources/Moltbot/VoiceWakeRuntime.swift +++ b/apps/macos/Sources/Moltbot/VoiceWakeRuntime.swift @@ -740,6 +740,8 @@ actor VoiceWakeRuntime { let token = trigger.lowercased().trimmingCharacters(in: .whitespacesAndNewlines) guard !token.isEmpty, let range = lower.range(of: token) else { continue } let after = range.upperBound + // Guard against index out of bounds when the trigger is at the end of the string + guard after <= text.endIndex else { continue } let trimmed = text[after...].trimmingCharacters(in: .whitespacesAndNewlines) return String(trimmed) } diff --git a/apps/macos/Tests/MoltbotIPCTests/VoiceWakeRuntimeTests.swift b/apps/macos/Tests/MoltbotIPCTests/VoiceWakeRuntimeTests.swift index 137df3ec7..10b1dfd8b 100644 --- a/apps/macos/Tests/MoltbotIPCTests/VoiceWakeRuntimeTests.swift +++ b/apps/macos/Tests/MoltbotIPCTests/VoiceWakeRuntimeTests.swift @@ -35,6 +35,19 @@ import Testing #expect(VoiceWakeRuntime._testHasContentAfterTrigger(text, triggers: triggers)) } + @Test func trimsAfterTriggerHandlesTriggerAtEnd() { + let triggers = ["clawd"] + let text = "hey clawd" + #expect(VoiceWakeRuntime._testTrimmedAfterTrigger(text, triggers: triggers) == "") + } + + @Test func trimsAfterTriggerHandlesEdgeCaseIndexBounds() { + // Regression test for crash when trigger range upperBound exceeds text.endIndex + let triggers = ["claude"] + let text = "claude" + #expect(VoiceWakeRuntime._testTrimmedAfterTrigger(text, triggers: triggers) == "") + } + @Test func gateRequiresGapBetweenTriggerAndCommand() { let transcript = "hey clawd do thing" let segments = makeSegments(