{"record":{"id":"919a4e1dfcbe4faf","repo":"sipeed/picoclaw","slug":"failed-to-close-discord-session-w","errorCode":null,"errorMessage":"failed to close discord session: %w","messagePattern":"failed to close discord session: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"pkg/channels/discord/discord.go","lineNumber":158,"sourceCode":"\n\t// Stop all typing goroutines before closing session\n\tc.typingMu.Lock()\n\tfor chatID, stop := range c.typingStop {\n\t\tclose(stop)\n\t\tdelete(c.typingStop, chatID)\n\t}\n\tc.typingMu.Unlock()\n\n\t// Cancel our context so typing goroutines using c.ctx.Done() exit\n\tif c.cancel != nil {\n\t\tc.cancel()\n\t}\n\tif c.progress != nil {\n\t\tc.progress.StopAll()\n\t}\n\n\tif err := c.session.Close(); err != nil {\n\t\treturn fmt.Errorf(\"failed to close discord session: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc (c *DiscordChannel) Send(ctx context.Context, msg bus.OutboundMessage) ([]string, error) {\n\tif !c.IsRunning() {\n\t\treturn nil, channels.ErrNotRunning\n\t}\n\n\tchannelID := msg.ChatID\n\tif channelID == \"\" {\n\t\treturn nil, fmt.Errorf(\"channel ID is empty\")\n\t}\n\n\tif len([]rune(msg.Content)) == 0 {\n\t\treturn nil, nil\n\t}","sourceCodeStart":140,"sourceCodeEnd":176,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/channels/discord/discord.go#L140-L176","documentation":"Stop() reports this when session.Close() errors while tearing down the Discord websocket. By this point the channel is already non-running (SetRunning(false)), typing goroutines and contexts are canceled, so this is best-effort cleanup noise — typically 'use of closed connection' or a failed websocket close handshake.","triggerScenarios":"Stop called twice (second Close errors), stopping after the network/voice connection already dropped, or the websocket close handshake failing during shutdown.","commonSituations":"Double-stop from manager plus signal handler, shutdown after network loss, orchestrators that call Stop on both SIGTERM and cleanup.","solutions":["Treat Stop errors as non-fatal: log a warning and continue shutdown","Guard against double Stop with an IsRunning/once check before closing the session","If the process is exiting anyway, a failed Close is harmless — the OS reclaims the socket"],"exampleFix":"// before\nif err := c.session.Close(); err != nil {\n    return fmt.Errorf(\"failed to close discord session: %w\", err)\n}\n\n// after\nif err := c.session.Close(); err != nil {\n    logger.WarnCF(\"discord\", \"session close error (ignored)\", map[string]any{\"error\": err.Error()})\n}\nreturn nil","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := ch.Stop(ctx); err != nil {\n    // shutdown is best-effort: log and continue teardown\n    log.Printf(\"warn: discord stop: %v\", err)\n}","preventionTips":["Guard Stop against double invocation (check IsRunning or use sync.Once)","Treat close errors as warnings in shutdown paths — the socket is reclaimed at process exit anyway","Stop channels in dependency order so voice/typing goroutines finish before session.Close()"],"tags":["discord","shutdown","websocket","cleanup"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}