Skip to content

fix(dictation): stop calling into the recorder after expo-audio released it - #3467

Merged
feruzm merged 1 commit into
developmentfrom
bugfix/dictation-done-crash
Aug 6, 2026
Merged

fix(dictation): stop calling into the recorder after expo-audio released it#3467
feruzm merged 1 commit into
developmentfrom
bugfix/dictation-done-crash

Conversation

@feruzm

@feruzm feruzm commented Aug 6, 2026

Copy link
Copy Markdown
Member

Closes #3466

Tapping Done in the dictation sheet closed the sheet and killed the app.

What was happening

Registered sheets unmount on hide (SheetProvider renders !visible ? null : <Sheet/>), so useDictationRecorder's unmount cleanup runs on every close, not "rarely" as its comment claimed.

useAudioRecorder is the first hook in useDictationRecorder, so its internal useReleasingSharedObject cleanup runs first and calls recorder.release(), detaching the JS object from its native counterpart. The cleanup declared after it then read recorder.isRecording, which throws NativeSharedObjectNotFoundException on iOS and InvalidSharedObjectIdException on Android.

Sheets render as siblings of <Application/> (src/index.tsx), so they sit outside the app's ErrorBoundary. The throw reached React Native's global handler as a fatal.

Change

  • The unmount cleanup no longer touches recorder. It clears the ticker and deactivates the recording session, nothing else.
  • Dropping the recorder.stop() leaks nothing: release() already stops a running recorder on both platforms (iOS sharedObjectWillRelease, Android sharedObjectDidRelease -> reset).
  • Side benefit: setAudioModeAsync({ allowsRecording: false }) used to be skipped because the throw happened on the line above it, leaving the iOS session active in .playAndRecord after dictation. It now runs.
  • Corrects the "sheets stay mounted" claim in CLAUDE.md and the comments written against it, and documents that sheets sit outside the ErrorBoundary.

Test plan

  • New src/hooks/useDictationRecorder.test.tsx mounts the hook against a mock mirroring expo-audio's real hook ordering, where the recorder throws on any member access after release. Verified it fails on the pre-fix code with Unable to find the native shared object associated with given JavaScript object (isRecording) and passes after.
  • yarn test:ci green: 738 passed, 1 skipped.
  • tsc --noEmit clean, eslint clean on changed files.
  • Device check worth doing: dictate, tap Done, and confirm no crash; then confirm the same for backdrop tap, back press, and opening then closing without recording, which all took the same path.

Summary by CodeRabbit

  • Bug Fixes

    • Improved dictation cleanup when closing the recording modal.
    • Prevented crashes caused by accessing released recording resources during unmount.
    • Ensured recording sessions are safely deactivated and pending status checks are cancelled.
  • Documentation

    • Clarified modal mounting, unmounting, state reset, error handling, and native-resource cleanup behavior.

…sed it

Tapping Done closed the dictation sheet and killed the app.

Registered sheets unmount on hide, so the hook's unmount cleanup runs on
every close. expo-audio's useAudioRecorder is the first hook in
useDictationRecorder, so useReleasingSharedObject's release() cleanup runs
first and detaches the JS recorder from its native counterpart. The cleanup
then read recorder.isRecording, which throws NativeSharedObjectNotFoundException
on iOS and InvalidSharedObjectIdException on Android. Sheets render as siblings
of <Application/>, outside the app's ErrorBoundary, so the throw reached React
Native's global handler as a fatal.

Dropping the recorder access leaks nothing: release() already stops a running
recorder on both platforms. It also lets the setAudioModeAsync call that the
throw used to skip actually run, so the iOS session is no longer left active in
playAndRecord after dictation.

Also corrects the 'sheets stay mounted' claim in CLAUDE.md and the comments
written against it.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The dictation sheet lifecycle documentation now reflects unmounting on hide. Recorder cleanup no longer accesses released native objects, and tests verify safe unmount behavior and recording-session deactivation.

Changes

Dictation lifecycle

Layer / File(s) Summary
Sheet lifecycle guidance
CLAUDE.md, src/components/dictationModal/dictationModal.tsx
Documentation and comments describe unmounting on hide, mount-time state reset, repeated cleanup, and errors outside the application error boundary.
Recorder unmount cleanup
src/hooks/useDictationRecorder.ts, src/hooks/useDictationRecorder.test.tsx
Unmount cleanup invalidates pending work, clears polling, and deactivates recording mode without accessing the released recorder. Tests simulate native release and verify safe cleanup.

Estimated code review effort: 2 (Simple) | ~10 minutes

Poem

A rabbit watched the sheet close tight,
The recorder vanished out of sight.
No native call was made too late,
Cleanup crossed the finish gate.
The session rests; the tests all cheer.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the dictation recorder crash fix caused by calls after expo-audio released the recorder.
Linked Issues check ✅ Passed The changes prevent released-recorder access, reset audio state, add regression coverage, and correct sheet lifecycle documentation for issue #3466.
Out of Scope Changes check ✅ Passed All code, test, comment, and documentation changes directly support the linked issue objectives.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bugfix/dictation-done-crash

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/hooks/useDictationRecorder.test.tsx (1)

2-2: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Avoid adding new react-test-renderer usage.

The repository does not provide a Testing Library renderer to migrate this test to, so keep only the existing usage in src/hooks/useDictationRecorder.test.tsx; adding a new react-test-renderer import contributes to deprecated React testing API usage.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/useDictationRecorder.test.tsx` at line 2, Remove the newly added
TestRenderer import and avoid introducing any additional react-test-renderer
usage in the useDictationRecorder tests; retain only the repository’s existing
usage in useDictationRecorder.test.tsx.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/hooks/useDictationRecorder.test.tsx`:
- Line 2: Remove the newly added TestRenderer import and avoid introducing any
additional react-test-renderer usage in the useDictationRecorder tests; retain
only the repository’s existing usage in useDictationRecorder.test.tsx.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 260b9dce-ae58-45b0-a1c1-d0d1c6a85454

📥 Commits

Reviewing files that changed from the base of the PR and between 7723af7 and ddde402.

📒 Files selected for processing (4)
  • CLAUDE.md
  • src/components/dictationModal/dictationModal.tsx
  • src/hooks/useDictationRecorder.test.tsx
  • src/hooks/useDictationRecorder.ts

@feruzm
feruzm merged commit 2d254c7 into development Aug 6, 2026
5 of 7 checks passed
@feruzm
feruzm deleted the bugfix/dictation-done-crash branch August 6, 2026 19:32
@feruzm

feruzm commented Aug 6, 2026

Copy link
Copy Markdown
Member Author

Re the react-test-renderer nitpick: declining it. react-test-renderer is the repo's only component-test renderer. There is no @testing-library/react-native in package.json or node_modules, and the two existing component tests (src/components/postCard/children/postCardContent.test.tsx, src/components/autoHeightImage/autoHeightImage.test.tsx) open with the identical import TestRenderer from 'react-test-renderer'. So this follows the established pattern rather than introducing a new one, and there is nothing to migrate to. Swapping renderers is a repo-wide decision, not something to settle in a crash fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dictation sheet: tapping Done crashes the app (native call on a released expo-audio recorder)

1 participant