fix(server): survive vocabulary merge conflicts and idle MCP streams - #199
Merged
Conversation
Two unhandled-error classes from the 2026-08-13 staging logs, both on a real customer project. Vocabulary saves (4x POST /merges 409 then an unhandled 500 at 14:04Z): GitHub answers a real merge conflict with 409 and the git provider re-throws it — only "already merged" is absorbed. The retry loop added for concurrent saves keyed on merged:false, a value that path never produces, so the exact conflict it was built for escaped as a 500 to the UI with no retry. The 409 is now caught inside the loop, the conflicted branch is dropped, and the write retries from fresh state. A PR fallback (protected main) no longer loops either: the vocabulary already landed on contentrain, so it proceeds to verification instead of re-writing the same change three times and reporting a bogus conflict. MCP Cloud SSE (3x unhandled UND_ERR_BODY_TIMEOUT): an MCP session's GET is a long-lived SSE stream that nothing pings — the SDK transport has no keepalive and neither does the loopback — so any session quiet for 300s hit undici's default bodyTimeout and died as an unhandled TypeError: terminated. The proxy hop now uses a dedicated undici Agent with the body timeout disabled (global fetches keep their safety net), and a stream that still breaks after headers are flushed ends the socket quietly instead of feeding a headers-sent response to the prod error handler (which threw ERR_HTTP_HEADERS_SENT into unhandledRejection).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Neden
13 Ağustos staging loglarındaki iki unhandled hata sınıfı — ikisi de gerçek müşteri projesinde (Lanista/collabers):
merged:false'a bakıyordu — o yoldan hiç gelmeyen bir değer. Yani döngünün var olma sebebi olan conflict, exception olarak kaçıp UI'a retry'sız 500 olarak düştü (logda 4×POST /merges → 409+ unhandled HttpError).UND_ERR_BODY_TIMEOUT): MCP oturumunun GET'i uzun ömürlü bir SSE akışı ve kimse ping'lemiyor (SDK transport'ta da loopback'te de keepalive yok) — 300 sn sessiz kalan her oturum undici'nin varsayılan bodyTimeout'una takılıp unhandledTypeError: terminatedile öldü.Ne değişti
vocabulary.patch: 409 artık döngü İÇİNDE yakalanıyor → conflict'li branch silinir → taze state'ten retry. PR-fallback (korumalı main) durumunda retry YOK: içerik zatencontentrain'de, doğrulamaya geçilir (aynı değişikliği 3 kez yazıp sahte 409 raporlamak yok). Diğer hatalar aynen yayılır.mcp-cloud-proxy: streaming hop'a özelundici.Agent({ bodyTimeout: 0 })(global fetch'lerin emniyet kemeri yerinde duruyor); header'lar gittikten sonra kopan stream sessizce kapanıyor — prod error handler'ınERR_HTTP_HEADERS_SENTkaskadı da kesildi.undicidoğrudan bağımlılık olarak eklendi.Test
pnpm testtam süit yeşil (149 dosya / 1285 test);mcp-cloud-proxy.integration.test.tsdavranış sözleşmesi değişmeden geçiyor🤖 Generated with Claude Code