By Juste Thales Gnimavo (CEO, ZeroSuite) and Claude Opus 5.5 (Claude Code)
Déblo Duo is a real-time interpreter app: each person speaks their language and hears the other in theirs. Why Déblo Duo exists explains the product. This post is about one mode of it, hands-free: one phone on the table between two people, the microphone always open, nobody pressing a button. It covers three days. On November 11, 2026, a bench told us what was wrong with the mode we had. On November 12, we found that the bench had been measuring a copy of the logic, not the code that ships. On November 13, version 1.1.6 went live on Google Play, and the CEO's first hands-free call was silent.
Why hands-free is hard
With two phones, or with push-to-talk, the app always knows who is speaking. With one phone and an open microphone, it does not. The same microphone hears person A, person B, and the phone's own speaker playing the translation. The system has to decide, for every sentence, which language it is, which direction should translate it, and what to do with the microphone while the translation plays.
Our hands-free mode at the time answered the last question simply: cut the microphone while a translation plays, so the phone does not translate its own voice. It sounded reasonable. Nobody had measured it.
November 11: the bench
Claude opened the session by replaying the brief against the code. The existing bench replayed one session in one direction. It could not reproduce one phone with two languages. So Claude wrote a new one: a two-language conversation replayed on one microphone, two translation sessions, one output track arbitrated the way the app arbitrates it. It ran seven variants, from push-to-talk (the reference) to several shapes of hands-free, on three sets of clips: clean conversation, very short phrases, and a background voice in a third language. Test languages were Chinese, Japanese, Spanish and Arabic, then French and English.
No production code was changed that day. The bench only measured.
The current hands-free variant lost speech on 71 of 72 turns. The reason was mechanical. Translation starts about 3 seconds after the start of a sentence. At that point the microphone is cut, because a translation is now playing. The end of the sentence never reaches the model. A speaker who said a price at the end of a sentence would have heard the price disappear.
The second finding was subtler. The direction that was supposed to translate detected the right language 215 times out of 215. The direction that had just translated did not. It stayed stuck on its language: 56 wrong detections out of 216, zero on the first turn, 29 on the fifth. The longer the conversation, the worse it got.
Opening a fresh session for that direction on each turn removed the drift. With that shape, on a clean conversation, the bench counted 72 turns with no duplicate and no wrong language, one turn never heard, and about 3.1 seconds before the first translated sound. Push-to-talk, the reference, was still faster: about 1.9 seconds.
That left a product decision, and it was Juste's. The choice was what Juste called the « film » behaviour: « micro jamais coupé, traduction au fur et à mesure ». The microphone is never cut; translation follows the speaker. Juste also set the price to pay for it: « Jusqu'à 4 s avant le premier son traduit, acceptable. » And asked the app to say where the mode belongs: hands-free is for a quiet place.
The session closed there. The server and the screen were left for a fresh session.
November 12: the bench was testing a copy
The next session started the same way, by replaying the brief against the code. Every symbol the brief named existed in the translation bridge, the module that runs in production. Then Claude checked what the bench imported.
It did not import the bridge. The bench had its own implementation of the logic. The 72 clean turns of November 11 measured the right shape of the algorithm on a copy of it, not the code that would ship.
That is an easy gap to miss. The copy had been written from the same design, by the same agent, the day before. It would have been natural to treat its numbers as numbers about production. They were not. A copy can be right while the real code is wrong, and the bench had no way to see it.
The fix was structural. The hands-free logic moved into a small pure module shared by the worker and the bench, and a new bench variant ran the worker's own direction object instead of a reimplementation.
It paid off on its first run. Claude's first version of the server change added a method to a dataclass, and the method had the same name as one of the dataclass's fields. In Python, that silently replaces the field. The language list passed to the translation session contained a function instead of a language code. No session opened, in any of the three modes, not only hands-free. py_compile passed. The file was syntactically fine.
The first pass of the bench on the real code failed with a validation error and the worker giving up. The method was renamed. Claude wrote the line that sums up the day in the session log: « Sans le banc sur le vrai code, ce défaut partait en prod. » Without the bench on the real code, this defect would have gone to production.
After the rename, the bench ran 234 turns on the code that ships: 72 on clean conversation, 18 in English, 72 with a background voice 15 dB below the speaker, 72 where the same person speaks twice in a row. No session errors, no reconnections, no failed warm-ups. On clean conversation: no duplicate, no wrong language, one turn never heard, 3.5 seconds before the first translated sound, inside the 4 seconds Juste had accepted. With the background voice: one duplicate, one wrong direction, five turns never heard.
The screen side was done the same day by two worker sessions, one after the other, on the mobile app.
November 13: 1.1.6, and a silent first call
Version 1.1.6 of Duo went to production on Google Play on the morning of November 13. On the App Store, the 1.1.6 build was attached to the version and submitted for review by Juste. The same morning, while fixing the store listings, we found that the app's own FAQ said the free trial lasted « 2 minutes » in every language. Production gives 10. The FAQ was corrected in the code; it goes out with the next build.
Then Juste made the first hands-free calls on the new version. Silence, on iPhone and on Android.
The worker's logs showed it. On Android, hands-free was active 4.2 seconds into the call, audio arrived for 47 seconds, and not one word was transcribed: digital silence. On iPhone, the call stayed mute until Juste left and re-entered it; then translation started five seconds later. In both logs, the event "microphone attached" and the event "hands-free on" were 0.1 seconds apart.
That was the clue. It was a race in the app. When the call connects, the app publishes the microphone and then mutes it, because calls start muted. But the worker's status could mark the call as live before that sequence finished. Hands-free then opened the microphone, and the startup mute ran after it and closed it again. Two events, a tenth of a second apart, in the wrong order.
The real fix belongs in the app: the mute call now waits until the microphone setup has finished. Claude wrote it and type-checked it. But it needs a new build, and the phones that already had 1.1.6 would stay broken until users updated.
So instead of spending a build, Claude added a safety net on the server, for phones already on 1.1.6. If hands-free is on and the worker hears nothing above silence from the host for a few seconds, it answers "off"; the app asks again and reopens the microphone. At most two retries, and only before the first live frame.
The last condition came from a mistake. The first version of the safety net misfired during normal conversation. Phones apply echo cancellation, and while a translation plays, echo cancellation produces silence on the microphone. To the safety net, that looked like a dead mic. It retried in the middle of a working call. The logs at 08:44 UTC on iPhone and 08:50 UTC on Android showed it. The rule was narrowed to the start of the call, before any live frame, and the worker was rebuilt.
What was proven, and what was not
Across the two worker rebuilds, the log Juste pasted showed five cold hands-free calls, each the first call after opening the app: at 08:44 UTC on iPhone and 08:50 UTC on Android (the two calls where the first version later misfired), then three more at 08:55 and 08:56. Five out of five translated, the first speech recognised 5 to 6 seconds after hands-free switched on. No silent call. Juste's verdict: « tout marche ».
We do not call it fixed, and the reason is in the same log. The safety net never fired before the first speech in any of those five calls. It was never seen repairing the case it was built for. The five calls prove that hands-free worked five times in a row on two phones that morning. They do not prove why. The fix that addresses the cause is in the app, and it has not shipped.
What stays open
- Short phrases in Chinese, Japanese, Spanish and Arabic were not measured with the final shape. A single one-second word is not transcribed in any mode, push-to-talk included.
- Switching sessions while someone is already speaking was not measured.
- Load per call: two live sessions plus a warm-up session per hands-free call. Exposure to
1011errors from the translation service is something to watch in production. - Synthetic voices: every bench number comes from synthetic speech, and the bench cannot hear the phone's speaker. Echo from the speaker into an open microphone, at high volume, is only proven on real devices.
- The app-side fix for the silent first call is written and type-checked. It has not shipped.
The lesson
A bench is only as honest as what it imports. On November 11, ours produced clean, useful numbers, and they drove a real product decision. They were also numbers about a copy. Nothing in the results said so. The only way to see it was to read the import lines of the bench on the next morning, as part of replaying the brief against the code.
The rule we took from it is short. A test that does not run the production module measures an intention. When the bench ran the real module, it found in its first minute a defect that a compiler had accepted and that would have opened no session for anyone. Two days later, production found a defect that no bench could have found, because it lived in the order of two events on a real phone. Both needed the real thing. That is also why the silent call stays open: five good calls are an observation, not an explanation.