Skip to content

GH-101: fix(discord): nil-conn deref in listen crashes process on network failure — snapshot conn under lock, no lockless g.conn.ReadJSON#102

Merged
alekspetrov merged 1 commit into
mainfrom
pilot/GH-101
Jul 14, 2026
Merged

GH-101: fix(discord): nil-conn deref in listen crashes process on network failure — snapshot conn under lock, no lockless g.conn.ReadJSON#102
alekspetrov merged 1 commit into
mainfrom
pilot/GH-101

Conversation

@alekspetrov

Copy link
Copy Markdown
Contributor

Summary

Automated PR created by Pilot for task GH-101.

Closes #101

Changes

GitHub Issue GH-101: fix(discord): nil-conn deref in listen crashes process on network failure — snapshot conn under lock, no lockless g.conn.ReadJSON

Context

Live daemon crash (2026-07-14, panic → SIGSEGV): the Discord gateway listen goroutine dereferences a nil websocket connection on network failure, crashing the whole process.

panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV]
gorilla/websocket.(*Conn).NextReader(0x0)   <- nil *Conn
discord.(*GatewayClient).listen.func1() transport.go:190

Root cause (sdk/integrations/discord/transport.go): listen at :190 calls g.conn.ReadJSON(&event) with NO nil-guard and NO lock (the func comment even says "Must NOT hold g.mu"). Meanwhile the reconnect/close path nils it: :88-89 g.conn.Close(); g.conn = nil. So a network failure nils g.conn while the read loop is mid-iteration → ReadJSON on nil → NextReader(0x0) → segfault. Note :144 (heartbeat) and :160 (resume) DO nil-check g.conn; :190 (the read loop) does not — inconsistent.

Implementation

In sdk/integrations/discord/transport.go:

  1. Snapshot the connection under lock at the top of each listen iteration: g.mu.Lock(); c := g.conn; g.mu.Unlock(); if c == nil { <handle: return or wait-for-reconnect> } — then call c.ReadJSON on the local c, never the shared field. This removes both the nil-deref AND the data race on g.conn.
  2. Audit ALL g.conn uses (lines ~98,102,126,149,167,190) for the same lockless-access pattern; make conn access uniformly lock-guarded/snapshotted.
  3. On nil/closed conn in the read loop, drive the existing reconnect path rather than panicking.

Acceptance

  • A nil/closed g.conn during listen does NOT panic — it returns cleanly or triggers reconnect (test: nil the conn mid-listen, assert no panic)
  • -race test on the listen/reconnect interleaving is clean (no data race on g.conn)
  • All g.conn accesses lock-guarded or snapshot-under-lock
  • Existing discord tests green

Refs

  • Crash on pilot daemon 2026-07-14 · studio-sdk@v0.30.0/sdk/integrations/discord/transport.go:190 vs guarded :144/:160

…crash (GH-101)

The gateway read loop called g.conn.ReadJSON directly with no lock and no
nil-guard, while the reconnect/close path nils g.conn under g.mu. A network
failure landing mid-iteration crashed the process with a SIGSEGV inside
gorilla/websocket's NextReader on a nil *Conn.

listen now snapshots g.conn under g.mu once per iteration and reads via the
local snapshot; a nil snapshot returns cleanly, which reconnectLoop already
treats as a signal to reconnect. This also removes the data race on g.conn
between listen and the reconnect/close path.
@alekspetrov
alekspetrov merged commit 185df84 into main Jul 14, 2026
3 checks passed
@alekspetrov
alekspetrov deleted the pilot/GH-101 branch July 14, 2026 16:00
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.

fix(discord): nil-conn deref in listen crashes process on network failure — snapshot conn under lock, no lockless g.conn.ReadJSON

1 participant