
Vi raderade 5 000 rader ljudkod — och transkript började dyka upp två gånger
GeekBye v1.6 slet ut två Swift-transkriberare på enheten och ersatte dem med en enda enhetlig pipeline — en nettoradering på över 5 000 rader, gjord medan folk var mitt i ett möte i appen. Sedan började transkript dyka upp två gånger, eftersom buggen flyttade till det enda lager som fortfarande trodde att det fanns två motorer.
Det finns en särskild sorts skräck i att bygga om koden som körs just nu, för människor som är mitt i ett möte. Du kan inte ta ner funktionen för underhåll — någon förlitar sig på den för att transkribera en konversation de inte kan upprepa. GeekBye v1.6 gjorde exakt det mot den mest kritiska vägen i appen: ljudpipelinen. Det här är berättelsen om vad vi raderade, vad som gick sönder och de två tillförlitlighetslärdomar som kom ur det.
Två motorer, en av dem i Swift
Före v1.6 kördes GeekByes tal-till-text på enheten i Swift-binärer. Det fanns två av dem: en som omslöt Apples Speech-ramverk, och en större (över 1 500 rader) som fångade dubbelt ljud — din mikrofon plus systemljud — via ScreenCaptureKit och öppnade sin egen WebSocket direkt till vår backend för att strömma till Deepgram. Två transkriberare, två infångningsvägar, två sätt att prata med nätverket, allt i ett kompilerat språk som innebar att varje ändring krävde att en binär kompilerades om.
v1.6.0 kollapsade allt detta till en enda pipeline. Den enda förenande commiten raderade Apple Speech-transkriberaren (~1 145 rader), Deepgram-Swift-infångaren (~1 523 rader), båda TypeScript-broarna, den gamla orkestratorn och IPC-hanterarna för dubbelt ljud — en nettoradering på över 5 200 rader. I deras ställe: renderaren fångar ljud genom standardiserade Web APIs, skickar det som PCM över IPC, och Electron-huvudprocessen äger en enda WebSocket-väg till backenden, med leverantören vald av backend-konfiguration. En infångningsväg, en socket-ägare, en plats att resonera kring.
Att radera femtusen rader känns fantastiskt. I ungefär en dag.
Buggen flyttade till lagret som fortfarande trodde att det fanns två
Sedan började transkript dyka upp två gånger. En enda talad mening skulle sparas som två identiska poster — ibland värre.
Grundorsaken är det mest lärorika i hela den här releasen, eftersom det är en allmän lag om förening. Vi hade förenat leverantörerna i huvudprocessen — en socket, en hanterare. Men React-lagret hade inte fått memot: det monterade fortfarande två leverantörsspecifika händelsekrokar samtidigt, en byggd för Deepgram och en för ElevenLabs. Båda lyssnade. Båda sparade. Varje transkript blev sparat av vilken krok som än var vid liv — och det var de båda.
Och det fanns en andra, listigare fördubbling under det: ElevenLabs sänder ut varje slutförd rad två gånger — en gång som en committed-händelse och igen som committed_with_timestamps. Så även med en enda krok kunde en mening anlända som två händelser.
Fixen (i v1.6-linjen) är en prydlig tvådelare som mappar exakt mot de två orsakerna: grinda varje händelsekrok bakom en enabled-flagga driven av backendens konfiguration för aktiv leverantör, så att bara en lyssnare någonsin är vid liv; och lägg till avduplicering av text per källa (kom ihåg den senast sparade raden för mikrofon och för systemljud) för att svälja ElevenLabs dubbelsändning.
Lärdomen är större än den här buggen: när du förenar två backends bakom ett gränssnitt försvinner inte dubbleringen — den migrerar till lagret som fortfarande antar att det finns två. Vi kollapsade rörsystemet i huvudprocessen och läckan dök upp två lager högre, i gränssnittets händelsekoppling. Att förena en pipeline är inte klart när producenten är förenad; det är klart när varje konsument har slutat tro på den gamla formen.
Den första återanslutningen: "ge upp efter fem försök"
v1.6.0 skeppade också GeekByes allra första automatiska WebSocket-återanslutning med exponentiell backoff — och den är värd att visa just för att den är den primitiva förfadern till den skottsäkra versionen vi kör idag.
Originalet var begränsat. Fem försök, fördröjningar på 1s, 2s, 4s, 8s, 16s — och sedan kapitulerade den, och sände ut ett fatalt fel: "Anslutningen förlorad — starta om transkriberingen." Vid tillfället kändes det ansvarsfullt: försök inte i all evighet, berätta ärligt för användaren. I praktiken var det en UX-bugg i förklädnad. En sextonsekunders nätverksblipp — en Wi-Fi-överlämning, en VPN-återanslutning, en tunnel — är tillfällig, men ett begränsat återförsök behandlar den som slutgiltig. Användaren gjorde inget fel och blev tillsagd att starta om ett pågående möte.
Det är exakt det felet som den nuvarande designen eliminerade. Idag återansluter GeekBye med obegränsad backoff och växlar till och med mellan leverantörer, och stannar bara någonsin av en genuint fatal anledning — autentisering, kvot, fakturering. Hela resan från "ge upp efter fem" till "ge aldrig upp på ett tillfälligt fel" berättas i varför ditt AI-anteckningsverktyg stannar på dålig Wi-Fi. v1.6 är där den vägen började, med den naiva versionen som var tvungen att existera först.
Föråldrade anslutningar hemsöker dig om de inte har namn
Ytterligare en tillförlitlighetslärdom landade i v1.6.3, och åtgärdade en subtil klass av bugg som alla som gör återanslutning över en tillståndsbärande backend förr eller senare kommer att möta.
När du återansluter — eller byter transkriberingsspråk mitt i sessionen, vilket återansluter under huven — dör inte den gamla anslutningen alltid tyst. Dess dödsrossling-meddelanden (CONNECTION_LOST, disconnected) kan anlända efter att den nya anslutningen redan är frisk, och riva ner den fullkomligt fungerande ersättaren. Fixen ger varje anslutningsförsök en identitet — ett sessions-ID per anslutning stämplat på WebSocket-URL:en — plus ett kort nådefönster efter återanslutning under vilket föråldrade frånkopplingsmeddelanden från ett tidigare försök ignoreras. Den skickar också ett uttryckligt stop-meddelande innan en socket stängs, så att backenden kan riva ner den gamla sessionen rent.
Principen: all återanslutningslogik över en tillståndsbärande backend behöver tagga varje försök med en identitet och behandla sena meddelanden från tidigare försök som brus — annars blir en lyckad återanslutning dödad av sin föregångares döende andetag.
Tre saker v1.6 lärde oss
- Förening flyttar buggar; den raderar dem inte. Kollapsa två motorer till en och granska varje konsument som fortfarande tror att det finns två. Vår var gränssnittet, som monterade två händelsekrokar efter att rörsystemet redan hade förenats.
- Din första återanslutning kommer att vara begränsad, och begränsad är fel för liveströmmar. "Ge upp efter N försök" förvandlar en tillfällig blipp till ett slutgiltigt fel som användaren får skulden för. Skilj fatalt (autentisering, kvot) från tillfälligt (en tappad socket) från allra första versionen.
- Återanslutningar behöver identitet. Tagga varje försök; ignorera de sena meddelandena från döda försök. En anslutning utan namn kan mördas av sitt eget spöke.
Detta är det andra kapitlet i tillförlitlighetsberättelsen som blir GeekBye v2. För det första, se auto-uppdateringssagan som tog sex releaser på fyra dagar (v1.5.x); för var återanslutningsarbetet till slut landade, varför ditt AI-anteckningsverktyg stannar på dålig Wi-Fi; och för hela bågen, anatomin av att skeppa mjukvara till perfektion.