Skip to content

fix(server): survive vocabulary merge conflicts and idle MCP streams - #199

Merged
ABB65 merged 1 commit into
mainfrom
fix/server-vocab-sse
Aug 14, 2026
Merged

fix(server): survive vocabulary merge conflicts and idle MCP streams#199
ABB65 merged 1 commit into
mainfrom
fix/server-vocab-sse

Conversation

@ABB65

@ABB65 ABB65 commented Aug 14, 2026

Copy link
Copy Markdown
Member

Neden

13 Ağustos staging loglarındaki iki unhandled hata sınıfı — ikisi de gerçek müşteri projesinde (Lanista/collabers):

  1. Vocabulary 500'ü (14:04Z): GitHub gerçek merge conflict'e 409 döner ve git provider bunu throw eder (yalnız "already merged" yutulur). fix(vocabulary): stop concurrent saves from dropping each other's terms #186'nın eşzamanlı-kayıt retry döngüsü 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).
  2. MCP Cloud SSE (3× 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 unhandled TypeError: terminated ile ö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 zaten contentrain'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 özel undici.Agent({ bodyTimeout: 0 }) (global fetch'lerin emniyet kemeri yerinde duruyor); header'lar gittikten sonra kopan stream sessizce kapanıyor — prod error handler'ın ERR_HTTP_HEADERS_SENT kaskadı da kesildi. undici doğrudan bağımlılık olarak eklendi.

Test

  • Yeni entegrasyon testleri: 409 → retry → başarı; PR-fallback → tek yazım + doğrulama; 401 gibi conflict-dışı hatalar aynen yayılır
  • pnpm test tam süit yeşil (149 dosya / 1285 test); mcp-cloud-proxy.integration.test.ts davranış sözleşmesi değişmeden geçiyor

🤖 Generated with Claude Code

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).
@ABB65
ABB65 merged commit c4d1f28 into main Aug 14, 2026
2 checks passed
@ABB65
ABB65 deleted the fix/server-vocab-sse branch August 14, 2026 17:25
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.

1 participant