{"record":{"id":"81392762358fc72a","repo":"microsoft/typescript-go","slug":"api-failed-to-write-response-v","errorCode":null,"errorMessage":"api: failed to write response: %v","messagePattern":"api: failed to write response: (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"internal/api/conn_sync.go","lineNumber":156,"sourceCode":"\tif c.timing != nil {\n\t\tc.timing.record(msg.Method, time.Since(start))\n\t}\n\n\tc.mu.Lock()\n\tdefer c.mu.Unlock()\n\n\tvar writeErr error\n\tif err != nil {\n\t\twriteErr = c.protocol.WriteError(msg.ID, &jsonrpc.ResponseError{\n\t\t\tCode:    jsonrpc.CodeInternalError,\n\t\t\tMessage: err.Error(),\n\t\t})\n\t} else {\n\t\twriteErr = c.protocol.WriteResponse(msg.ID, result)\n\t}\n\n\tif writeErr != nil {\n\t\tpanic(fmt.Sprintf(\"api: failed to write response: %v\", writeErr))\n\t}\n}\n\n// handleNotification processes an incoming notification.\nfunc (c *SyncConn) handleNotification(ctx context.Context, msg *Message) {\n\t_ = c.handler.HandleNotification(ctx, msg.Method, msg.Params)\n}\n\n// Call sends a request to the client and waits for a response.\n// This method is safe to call from multiple goroutines - calls are serialized.\nfunc (c *SyncConn) Call(ctx context.Context, method string, params any) (json.Value, error) {\n\t// Serialize all Call operations. This is critical because:\n\t// 1. The msgpack protocol uses method names as response IDs\n\t// 2. The handler code (project internals) may spawn goroutines that call\n\t//    filesystem callbacks concurrently\n\t// 3. We need to ensure write/read pairs are atomic\n\tc.mu.Lock()\n\tdefer c.mu.Unlock()","sourceCodeStart":138,"sourceCodeEnd":174,"githubUrl":"https://github.com/microsoft/typescript-go/blob/1bcfa18d79a3be41772223d5c05dfe4480e614ff/internal/api/conn_sync.go#L138-L174","documentation":"SyncConn.handleRequest finished handling an incoming request (success or error) but protocol.WriteResponse/WriteError to the underlying io.ReadWriteCloser failed. A response that cannot be delivered leaves the framing stream unusable, so the code panics to abort the connection instead of continuing.","triggerScenarios":"Any request (e.g. getDiagnostics or a project-config load) completes after the client closed its side; the write returns io.EOF, EPIPE, or 'use of closed network connection'. Also fires for the getServerTiming meta-requests when their writes fail.","commonSituations":"Client timeout shorter than server processing time (request abandoned, socket closed); client crash or restart mid-request; test doubles that close the pipe right after sending a request; stdout transport closed by a spawning process.","solutions":["Compare the client's timeout with actual server processing time and raise it or make the call async","Check for proxies/load balancers killing long-lived connections","Make the client read responses to EOF before closing after sending requests","Wrap SyncConn.Run with a recover to log and drop the dead connection instead of crashing"],"exampleFix":"// before\nctx, cancel := context.WithTimeout(ctx, 500*time.Millisecond) // server needs 2s\n\n// after\nctx, cancel := context.WithTimeout(ctx, 10*time.Second)\ndefer cancel()","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"defer func() {\n\tif r := recover(); r != nil {\n\t\tif strings.Contains(fmt.Sprint(r), \"api: failed to write response\") {\n\t\t\tlog.Printf(\"client went away mid-request: %v\", r)\n\t\t\treturn // drop connection, do not crash\n\t\t}\n\t\tpanic(r)\n\t}\n}()","preventionTips":["Set client timeouts comfortably above worst-case server processing time","Drain responses to EOF on the client before closing after requests","Log and recycle the connection on write panics instead of letting them propagate"],"tags":["go","panic","connection","write-error","jsonrpc","timeout"],"backgroundTag":null,"analyzedSha":"1bcfa18d79a3be41772223d5c05dfe4480e614ff","analyzedAt":"2026-08-16T02:12:00.115Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}