Skip to content

Commit 0aec47b

Browse files
authored
Merge pull request #7 from ethpandaops/fix/send-on-closed-channel-panic
fix: prevent send-on-closed-channel panic in AppServerAdapter during …
2 parents dc0851e + 1aed60c commit 0aec47b

1 file changed

Lines changed: 24 additions & 1 deletion

File tree

internal/subprocess/appserver_adapter.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"maps"
99
"strings"
1010
"sync"
11+
"sync/atomic"
1112

1213
"github.com/ethpandaops/codex-agent-sdk-go/internal/config"
1314
sdkerrors "github.com/ethpandaops/codex-agent-sdk-go/internal/errors"
@@ -73,6 +74,10 @@ type AppServerAdapter struct {
7374
pendingRPCRequests map[string]int64
7475
sdkMCPServerNames map[string]struct{}
7576

77+
// messagesClosed is set before closing the messages channel so that
78+
// senders on other goroutines can avoid a send-on-closed-channel panic.
79+
messagesClosed atomic.Bool
80+
7681
wg sync.WaitGroup
7782
}
7883

@@ -1408,7 +1413,10 @@ func (a *AppServerAdapter) handleUserMessage(
14081413
// translates them into exec-event format messages.
14091414
func (a *AppServerAdapter) readLoop() {
14101415
defer a.wg.Done()
1411-
defer close(a.messages)
1416+
defer func() {
1417+
a.messagesClosed.Store(true)
1418+
close(a.messages)
1419+
}()
14121420
defer close(a.errs)
14131421

14141422
notifications := a.inner.Notifications()
@@ -2268,6 +2276,21 @@ func (a *AppServerAdapter) injectControlResponse(
22682276
requestID string,
22692277
responseData map[string]any,
22702278
) {
2279+
// Guard against sending on a closed channel. The readLoop goroutine
2280+
// closes a.messages when the inner transport shuts down, which can
2281+
// race with control-request handlers still running on other goroutines.
2282+
// The atomic check handles the common case; the recover covers the
2283+
// narrow window between the check and the send.
2284+
if a.messagesClosed.Load() {
2285+
return
2286+
}
2287+
2288+
defer func() {
2289+
if r := recover(); r != nil {
2290+
a.log.Debug("inject suppressed: messages channel closed during shutdown")
2291+
}
2292+
}()
2293+
22712294
msg := map[string]any{
22722295
"type": "control_response",
22732296
"response": responseData,

0 commit comments

Comments
 (0)