DEV Community

Roronoa
Roronoa

Posted on

Keep Partial Transcripts Out of Crash Upload Payloads

You join the voice-notes repo on a Monday morning, and the open bug is already older than your laptop setup. The on-device recognizer drops the last partial utterance when an incoming phone call kills the app process. In the first hour you add a crash breadcrumb that stores that partial text so the next fatal error is easier to explain. By the afternoon that same string sits inside a vendor upload collected from the shared QA phone on the desk.

This opening is a proposed failure path, not a measurement taken from a named handset anywhere in this article. You should treat every version, permission bit, and power state below as a field you fill on your own device. The goal is to stop your first pull request from teaching the crash pipeline how to remember spoken content. A redacted breadcrumb can still explain the process death without copying the utterance into the outbound upload.

What you inherit in the first hour

Separate this leak from older ones

You are not retesting keyboard learning, phone backup, recents capture, or the remote debug host during this pass. Those leaks need their own experiments, and mixing them in one matrix hides which control actually failed. This pass covers crash breadcrumbs, native tombstones, and the log buffer that a reporter attaches after the process dies. If a unique sentence can be rebuilt from any of those three places, the first-hour patch is not finished.

Write the environment header first

Write the device model, OS build, framework version, and crash SDK version at the top of your notes. Also record microphone permission, network type, and whether the phone is on battery saver or wall power. A junior rollback is safer when that header remains in the pull request after the diff has already moved. Without the header, the next person cannot separate a scrub failure from an OS change they never reproduced.

Decide what a breadcrumb may keep

Use a contract, not a vibe

Use the table as the contract for the first pull request, and review every row before you add an SDK call. It is a review aid, not a claim that every crash vendor behaves the same way on every OS build. If your pinned SDK disagrees with a row, follow the current vendor guide and record that exception in the pull request. Do not copy an older sample that attaches the raw message string just because a tutorial did that last year.

Field Allowed in crash payload Reason
Partial transcript text No Speech content is user data, even when recognition ran on device.
Transcript length bucket Yes, coarse only Empty, short, or long helps triage without storing words.
Locale tag Yes Language id explains model choice and does not reveal the utterance.
Model package id Yes, if non-secret Identifies the on-device bundle you shipped, not the audio.
Audio bytes or file path No Paths often include user ids, and bytes are the recording itself.
Permission state at crash Yes Revoked versus granted microphone changes the expected failure.
Lifecycle transition Yes Background, call interruption, or low-power entry is the variable.
Exception class and stack Yes, after scrub Keep the type, and drop messages that echo the utterance.

When support asks for the words

If a teammate says support cannot debug the death without the words, move that debate out of the first hour. Ship the redacted payload first, then design a consented debug build that never ships on the public store track. A store build that sometimes includes speech is harder to roll back than a flag that never attached the text. Keep the debate in the ticket, and keep the words out of the payload while the team waits for a decision.

Build the breadcrumb in one place

Proposed Kotlin scrub

Put the scrub in one function so a later feature cannot invent a second logger that still holds the raw text. The Kotlin snippet below is proposed example code, and you should rename the types to match the crash SDK you link. It has not been timed on a device for this article, so do not read it as a latency or battery result. Run it only after you confirm the method names against the SDK version pinned in your dependency lockfile.

data class VoiceCrashCrumb(
    val lengthBucket: String,
    val locale: String,
    val modelPackageId: String,
    val micGranted: Boolean,
    val lifecycle: String,
    val errorClass: String,
)

fun lengthBucket(partial: String?): String = when {
    partial.isNullOrEmpty() -> "empty"
    partial.length < 40 -> "short"
    partial.length < 160 -> "medium"
    else -> "long"
}

fun voiceCrumb(
    partial: String?,
    locale: String,
    modelPackageId: String,
    micGranted: Boolean,
    lifecycle: String,
    error: Throwable,
): VoiceCrashCrumb {
    // Never pass partial or error.message into the SDK map.
    return VoiceCrashCrumb(
        lengthBucket = lengthBucket(partial),
        locale = locale,
        modelPackageId = modelPackageId,
        micGranted = micGranted,
        lifecycle = lifecycle,
        errorClass = error::class.java.simpleName,
    )
}
Enter fullscreen mode Exit fullscreen mode

Register the same rule on iOS and cross-platform shells

Never pass the partial string or the exception message into the map that the crash SDK will serialize and send. On iOS, apply the same rule to every custom key you set before the reporter builds its outbound payload. Metric and diagnostic APIs differ by OS version, so read the guide that matches the SDK version you actually link. If that SDK offers a log scrubber callback, register it beside the crumb builder rather than in a random extension.

A second registration point is how the first rollback leaves one path still attaching speech after the flag looks off. Search the repo for the partial variable name before review, including instrumentation tests and leftover sample activities. A unit test that asserts the serialized map lacks that variable is worth more than a comment about being careful. Add the test in the same pull request so a rollback cannot delete the only guard you had.

If you are in React Native or Flutter, keep the scrub on the native crash bridge, not only in the Dart or JavaScript logger. A redacted JS breadcrumb does nothing once the native module still forwards the utterance in its own map. Name that bridge method in the pull request so the reviewer knows which side you actually changed. Cross-platform shells fail this test when only one language was searched for the partial string before review.

Run the first-hour experiment

Follow one order

The steps below are a procedure for you to run, not a result reported from a lab phone in this article. Fill the environment header, follow the order, and stop when the payload still contains the unique synthetic sentence. A pass on a plugged-in phone does not prove the same scrub under battery saver or after a microphone revoke. Those transitions belong in this pull request because they are how on-device voice sessions actually die in the field.

  1. Record device model, OS version, app version, crash SDK version, network type, battery saver state, and microphone permission before you start.
  2. Install a debug build that calls the crumb builder, and keep any raw logger behind a flag that defaults to off.
  3. Speak a unique synthetic sentence that appears nowhere else in the repo, using an animal name plus a four-digit code.
  4. Force a crash from the voice screen after the partial text is visible, using the test hook you already ship for QA.
  5. Collect the local crash file or the debug upload, then search for that sentence, the raw partial, and the exception message.
  6. Repeat the crash once after you background the app, and once after you revoke the microphone and return to the same screen.
  7. Toggle airplane mode before the reporter flushes, restore the network, and search the retried payload for the same sentence.
  8. Confirm the payload still has the length bucket, locale, model package id, permission bit, lifecycle label, and exception class.

Capture commands you can adapt

Replace the sample package name, and do not collect logcat from a phone that already holds real user audio from customers. If the search tool in the sample is missing on your host, use grep with the same pattern and paste the command into the pull request. On a shared QA phone, clear the log buffer before the run so an older session cannot masquerade as a fresh pass. A dirty buffer is the usual reason a junior marks the scrub finished while yesterday's sentence is still sitting there.

adb shell getprop ro.build.version.release
adb shell dumpsys package com.example.voicenotes | grep -i permission
adb logcat -c
adb logcat -d -t 200 | grep -E "transcript|voiceCrumb|PARTIAL"
Enter fullscreen mode Exit fullscreen mode

Record the iOS outcome separately

iOS needs a different capture path, because Console and the crash reporter do not share a single ring buffer. Export the pending report from the device, or open the SDK debug viewer, then search for the same unique sentence. Write whether that report recovered after relaunch, restarted as a new session, or silently disappeared from the pending queue. Keep that observation next to the Android notes, and do not invent a battery figure to make the two platforms look comparable.

Roll back without restoring the leak

Your first rollback should disable the flag that attaches breadcrumbs, not delete the scrub function from the module. If the redacted crumb crashes inside the crash SDK, turn attachment off and keep forbidden fields out of every other logger. Do not fix a bad upload by putting the raw partial back while you wait for a vendor support reply. That shortcut becomes the next release incident, and it is harder to see because the flag was described as temporary.

A rollback note in the pull request can stay this concrete:

  • The attachment flag defaults to false on the store track, and a debug build may enable it only after the unique-sentence search passes.
  • Reverting that flag does not revert the rule that partial text never enters the upload map, the log line, or the native message.
  • If QA needs the words, they use a local-only overlay that the release build compiles out, rather than a hidden gesture in production.

If the flag-off build still uploads the sentence, the leak sits outside this patch and needs a wider search before you blame the crumb. Check analytics events, native standard error, and any attach-log checkbox that the reporter settings screen still leaves enabled. Also search for string interpolation that builds a log line before your scrub function has a chance to run. The first hour is long enough to miss a debug print that a sample activity left behind during the previous sprint.

Draft fixtures away from production speech

You will want synthetic utterances and a second reading of the scrub rules before you open the first pull request. Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode's free model access and free server option can host that drafting when you paste only synthetic sentences and the redaction function. Do not paste customer audio, device logs, or crash archives just because that server is free and the first hour feels short.

This article does not name a model, a token quota, a machine shape, or a duration you should plan a release around. An allowance you remember from an older post is not evidence, so treat the live limit as unknown until the project page states it. If the project publishes a repository or a pricing note, read that page yourself rather than trusting a recap from chat. Use the free model access on the free server to generate length-bucket cases from invented sentences only.

Then ask whether a proposed log line still holds recoverable speech, after you confirm the current terms on the project page. If your workplace forbids external code tools, skip the drafting server and review the contract table with a teammate on your own network. The handset experiment still stands on its own, and a local review is enough to open a careful first pull request. A free server is only a convenience for synthetic fixtures, not a store for the speech this patch is trying to keep on device.

Who should skip this pattern

Skip this pattern when you must retain verbatim speech for a medical, legal, or accessibility record inside an already approved system. Also skip it when the crash SDK cannot attach custom keys without also capturing the full log buffer on that OS version. In that case the first pull request should disable log attachment, rather than encode the sentence into a clever numeric bucket. A one-character length bucket is the transcript with extra steps, and review should reject that encoding as a leak.

Employee debug screens can remain when they are local-only and compiled out of the release variant you actually ship. They must not call the upload API, even behind a hidden gesture that the QA group believes nobody outside the team will find. Hidden gestures leak because crash reporters and screen recordings both outlive the person who added them during onboarding. If you need consented capture, put it in a separate build flavor whose name a reviewer cannot miss in the diff.

What you should send back

Reply with the device, the OS build, the crash SDK version, and the lifecycle step you actually executed on that hardware. Say whether the unique sentence recovered in a later upload, stayed only in the local log, or disappeared after the scrub. If you rolled the flag back, say whether the flag-off build still contained the sentence inside some other attachment. Those three outcomes are more useful than a vague note that the crash flow felt acceptable on your desk.

This article measured no handset, and vendor behavior changes with the SDK version you pin during the following week. A scrub that passes a logcat search can still fail inside a native minidump or an attached console export from the same run. Re-read the SDK notes for your exact dependency before you merge, and link that note from the pull request description. If you cannot name the device and the transition, you do not yet have a result, only a plan you still need to run.

Top comments (0)