microsoft/typescript-go · critical
api: failed to write response: %v
Error message
api: failed to write response: %v
What it means
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.
Source
Thrown at internal/api/conn_sync.go:156
if c.timing != nil {
c.timing.record(msg.Method, time.Since(start))
}
c.mu.Lock()
defer c.mu.Unlock()
var writeErr error
if err != nil {
writeErr = c.protocol.WriteError(msg.ID, &jsonrpc.ResponseError{
Code: jsonrpc.CodeInternalError,
Message: err.Error(),
})
} else {
writeErr = c.protocol.WriteResponse(msg.ID, result)
}
if writeErr != nil {
panic(fmt.Sprintf("api: failed to write response: %v", writeErr))
}
}
// handleNotification processes an incoming notification.
func (c *SyncConn) handleNotification(ctx context.Context, msg *Message) {
_ = c.handler.HandleNotification(ctx, msg.Method, msg.Params)
}
// Call sends a request to the client and waits for a response.
// This method is safe to call from multiple goroutines - calls are serialized.
func (c *SyncConn) Call(ctx context.Context, method string, params any) (json.Value, error) {
// Serialize all Call operations. This is critical because:
// 1. The msgpack protocol uses method names as response IDs
// 2. The handler code (project internals) may spawn goroutines that call
// filesystem callbacks concurrently
// 3. We need to ensure write/read pairs are atomic
c.mu.Lock()
defer c.mu.Unlock()View on GitHub (pinned to 1bcfa18d79)
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
Example fix
// before ctx, cancel := context.WithTimeout(ctx, 500*time.Millisecond) // server needs 2s // after ctx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel()
Defensive patterns
Strategy: try-catch
Try / catch
defer func() {
if r := recover(); r != nil {
if strings.Contains(fmt.Sprint(r), "api: failed to write response") {
log.Printf("client went away mid-request: %v", r)
return // drop connection, do not crash
}
panic(r)
}
}() Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- api: failed to write panic error response: %v (original pani
- unknown callback name: %s
- -32603
- -32603
- api: remote error [%d]: %s
AI-assisted analysis of microsoft/typescript-go@1bcfa18d79 (2026-08-16).
Data as JSON: /api/errors/81392762358fc72a.
Report an issue: GitHub.