Bug-hunt v2: fix 41 bugs (fuzzing + dogfood pass)#24
Conversation
- H1: Ignore hotkey press while transcription is still processing (no more misleading "recordingStartFailed" toast). - H2: Ignore press-and-hold key-down during transcription processing. - H3: Always stop active recorder on hotkey press regardless of the immediateRecording toggle — prevents stranded recorder when the user flips the setting mid-recording. - H4: Observe NSWorkspace.willSleepNotification and AVAudioEngineConfigurationChange in AudioEngineRecorder so sleep / route changes commit (or surface) the recording instead of silently truncating it. Observers torn down on stop/cancel/deinit. - H5: Make MicrophoneVolumeManager @mainactor so concurrent boost/restore from AudioEngineRecorder no longer race on originalVolume / isVolumeBoosted (mic-stuck-at-100% bug). - M10: PressAndHoldKeyMonitor.stopWatchdog now invalidates the Timer synchronously (capturing it into a local) and marshals to the main RunLoop only for the actual invalidate call. Prior async-Task approach lost the timer reference when called from deinit. - M11: MicTestCapture throttles the per-buffer MainActor Task to ~60Hz so audio callbacks can't pile up an unbounded queue while main stalls. - M14: Surface a user-visible alert + log when showRecordingWindowForProcessing can't build the recording window (audioRecorder/ModelContainer nil), instead of silently calling the completion. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- H6: add isStarting guard so queued ensureDaemonRunning callers wait for an in-flight startProcess instead of launching a duplicate daemon - H7: convert applicationWillTerminate's fire-and-forget shutdown into applicationShouldTerminate with .terminateLater so the Python daemon is fully shut down before the app exits - H8: drain stdout/stderr concurrently in UvBootstrap.runProcess BEFORE waitUntilExit so a child writing >64KiB can't deadlock on a full pipe - H9: move the synchronous stdin write off the actor via Task.detached so a blocked write can't hold actor isolation against handle(line:) - M6: cap each MLDaemon JSON-RPC payload at 1 MiB and reject oversize inputs early instead of DoSing the Python side - M7: enforce a 64 KiB size cap on the MLX system-prompt file and add utf16 + lossy-utf8 fallbacks with warnings logged to Console.app - M8: clean up pipes/readabilityHandler in MLDaemon startProcess catch block when proc.run() throws - M9: clear readability handlers and close file handles in the two MLXModelManager download-spawn catch blocks - L1: reverse HF cache directory names via an explicit known-repos table so repo names containing "--" round-trip losslessly - L4: stop listening (readabilityHandler = nil) inside MainActor.run BEFORE clearing progress so a late callback can't restore a stale "Downloading..." string Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- C1: Welcome window red-close button now sets hasCompletedWelcome so the next hotkey isn't silently dropped by WindowController's guard. - H15: When microphone state is .denied, "Request Access" now routes to System Settings → Privacy → Microphone (macOS won't re-prompt). - H16: Verify MLX failure now always overwrites the in-progress "Checking model…" string with a concrete error (including stderr detail when available). - H17: Both MLX screens use a shared helper, nextSelectionAfterDeletion, that prefers an already-downloaded model and falls back to "no MLX model installed" — no more swapping to an undownloaded fallback or the two screens disagreeing. - H18: Categories "Reset" now opens an NSAlert confirmation describing that custom categories will be deleted, matching the existing TranscriptionHistoryView pattern. - M1: MLX download progress bar in the correction picker is now indeterminate (no fake 0% determinate bar) since Python downloads don't report fractional progress; MLXModelManagementView already used an indeterminate spinner via UnifiedModelRow. - M2: "Open Recordings Folder" was opening the system temp directory. Renamed to "Open App Data Folder" and pointed at the AudioWhisper Application Support folder, which actually holds persistent data. - M3: AccessibilityPermissionManager now invokes completion(false) when a polling chain is superseded so the caller's UI moves out of .requesting instead of being stuck. - M4: Category editor now rejects an empty/whitespace identifier with a clear validation error before checking for duplicates. - M5: Category editor rejects non-SF-Symbol icon strings with a clear validation error so saved categories don't render as blank glyphs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- H10: Add [weak self] to KeyboardEventHandler NSEvent monitor closures so deinit fires and monitors are removed - H11: Cancel `tasks` (async streams) and `handlerTasks` (in-flight handlers) in NotificationCoordinator.deinit - H12: Add deinit to RecordingViewModel that cancels notificationTasks when the SwiftUI view tree drops the VM without onDisappear - H13: Replace `guard !isLoading` drop pattern in TranscriptionHistoryViewModel with cancel-and-retry plus 200ms debounce so the latest search wins - H14: Same fix as H13 covers the delete-record reload path - H22: Clear WindowController.storedTargetApp in hideWindow and after restoreFocusToPreviousApp so stale NSRunningApplication doesn't survive across sessions - L3: Cap AppCategoryManager.userMappings at 200 entries with deterministic alphabetical eviction; also enforced on load to trim oversized existing storage Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- H19: CategoryStore no longer crashes on duplicate ids in categories.json; deduplicates in-memory and heals on disk - H20: SemanticCorrectionService.safeMerge caps Levenshtein at 4000 chars and falls back to length-ratio heuristic for long transcripts - H21: KeychainError gains osStatusError(OSStatus) so non-success/non-not-found statuses surface real OSStatus instead of collapsing to itemNotFound - L2: MLXModelManager.deleteModel validates repo identifier (rejects "..", leading "/", null bytes, control chars, non-org/name shapes) - M12: TranscriptionError no longer routes generic "parakeet"/"python" messages to pythonConfigurationError unless config keywords are present - M13: Adds structural NSError domain/code matcher to TranscriptionError.from(error:) and ErrorPresenter.showError(_:Error) so localized macOS systems still get correct recovery affordances; English substring matcher preserved as fallback Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (41)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
- Break two long alert strings (AppDelegate+RecordingWindow.swift, DashboardCategoriesView.swift) - Replace String(data:encoding:) with String(bytes:encoding:) in UvBootstrap+Process.swift and MLXCorrectionService.swift (drops intentional lossy String(decoding:as:) — log warns instead) - Refactor TranscriptionError.from(errorMessage:) into per-domain helpers (cyclomatic complexity 16 → ≤15) and drop the literal TODO marker in the M13 doc comment - Fold single-if for-loops in MLXModelManager and CategoryStore into where-clauses (for_where) - Extract H4 interruption observers + downsampleForDisplay into AudioEngineRecorder+Interruptions.swift (type body length 305 → ≤250) - Split M13 structural-matcher tests into TranscriptionErrorTests+Structural.swift Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|


Summary
Second multi-agent bug hunt against AudioWhisper (after PR #22 fixed 44). This round was a fuzzing + dogfood pass: 5 hunter agents → 41 raw candidates → 4 validators → 41 confirmed actionable bugs → 5 Opus workers in isolated worktrees → 5 Opus validators reviewed each commit.
Final review: 37 SOLID / 4 WEAK / 0 BROKEN. Full test suite green: 2805 tests, 0 failures, 37 skipped.
Bugs fixed by severity
CRITICAL (1)
C1Closing Welcome window via red dot silently broke recording foreverHIGH (22)
H1Hotkey during processing → second-recording attempt + misleading toastH2Same flaw on push-and-hold pathH3Toggling Immediate Recording mid-record stranded the recorderH4No sleep / engine-config observer → silent truncation on lid closeH5MicrophoneVolumeManager unsynchronized → mic stuck at 100%H6Concurrent daemon spawn during restart → orphan subprocessH7applicationWillTerminatefire-and-forgot daemon shutdownH8uv syncpipe deadlock on first launch (>64KiB stdout)H9Daemon stdin write inside actor → mutual deadlockH10KeyboardEventHandler retain cycle (NSEvent closures captured self strongly)H11NotificationCoordinator deinit skipped async-stream + handler tasksH12RecordingViewModel had no deinit → 3 notification loops survived dropped VMH13Search-while-pagination silently dropped keystrokesH14Delete-record reload skipped during paginationH15"Request Access" was a silent no-op once mic was deniedH16Verify MLX failure left "Checking model (offline)…" foreverH17Deleting selected MLX model swapped to a possibly-undownloaded fallback; two screens disagreedH18Categories Reset had no confirmation → silently wiped custom promptsH19CategoryStore crashed on launch ifcategories.jsonhad duplicate idsH20Unbounded Levenshtein insafeMerge→ multi-second UI freeze on long transcriptsH21KeychainService collapsed all errors into.itemNotFound(Time Machine restore looked like "no key set")H22storedTargetAppnever cleared → stale paste target after app switchMEDIUM (14)
M1MLX download progress bar hardcoded to 0% → indeterminate spinnerM2"Open Recordings Folder" opened system temp dir → renamed to "Open App Data Folder" pointing at~/Library/Application Support/AudioWhisperM3AccessibilityPermissionManager abandoned completion handler on supersession → UI stuck on "requesting"M4Category editor saved empty/whitespace identifierM5Category icon field accepted any string; invalid SF Symbols rendered blankM6MLDaemon transcribe/correct had no input size cap (now 1 MiB)M7MLX system-prompt file: no size cap + silent encoding fallback (now 64 KiB cap + utf16/lossy-utf8 fallbacks with warning logs)M8MLDaemon pipes leaked onproc.run()throwM9MLXModelManager pipes leaked on download spawn throwM10PressAndHold watchdog Timer never invalidated when deinit-while-pressedM11MicTestCapture spawned a MainActor Task per audio buffer (~47/s) → 60Hz throttleM12Any "parakeet" in an error message → "Configure Python" misleading recovery → tightenedM13Error-recovery affordances used English substring matching (partial: added NSError-domain matcher; substring stays as fallback)M14showRecordingWindowForProcessingsilently swallowed whenaudioRecorderwas nilLOW (4)
L1HF--//reversal was lossy for repos with--in slug → forward escaped-to-repo lookup map firstL2MLXModelManager.deleteModelused unvalidated repo string → strictorg/namevalidatorL3AppCategoryManager.userMappingsgrew unbounded → 200-entry cap with deterministic evictionL4readabilityHandlercleared afterMainActor.run→ stale "Downloading…" string after success → moved clear inside the MainActor block, beforeremoveValueDocumented follow-ups (4 WEAK items — functionally correct, not blocking)
.recordingStartFailedon route-change interrupt; the recording was already in progress, not failing to start. A dedicated.recordingInterruptednotification would be more accurate.@MainActoronMicrophoneVolumeManager. Functionally correct, but pins Core Audio HAL property setters to the main thread. Anactorwould be a tighter fit.isStarting. Functionally correct (cancellation isn't needed in practice), but a stored Task reference would be cleaner.Methodology
bug-hunt-2-fixesfrommaster @ ad33346swift testsequential: 2805 tests, 0 failures, 37 skipped (~63s)swift buildcleanTest plan
swift buildcleanswift test(sequential) — 2805 passed, 0 failed, 37 skippedbuild-and-test,lint,CodeQL,SonarCloud(run by GitHub Actions)🤖 Generated with Claude Code