{"record":{"id":"4f4793c9e422b377","repo":"cli/cli","slug":"tunnel-closed-w","errorCode":null,"errorMessage":"tunnel closed: %w","messagePattern":"tunnel closed: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/cmd/codespace/jupyter.go","lineNumber":101,"sourceCode":"\tgo func() {\n\t\topts := portforwarder.ForwardPortOpts{\n\t\t\tPort: serverPort,\n\t\t}\n\t\ttunnelClosed <- fwd.ForwardPortToListener(ctx, opts, listen)\n\t}()\n\n\t// Server URL contains an authentication token that must be preserved\n\ttargetUrl := strings.Replace(serverUrl, fmt.Sprintf(\"%d\", serverPort), fmt.Sprintf(\"%d\", destPort), 1)\n\terr = a.browser.Browse(targetUrl)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to open JupyterLab in browser: %w\", err)\n\t}\n\n\tfmt.Fprintln(a.io.Out, targetUrl)\n\n\tselect {\n\tcase err := <-tunnelClosed:\n\t\treturn fmt.Errorf(\"tunnel closed: %w\", err)\n\tcase <-ctx.Done():\n\t\treturn nil // success\n\t}\n}\n","sourceCodeStart":83,"sourceCodeEnd":106,"githubUrl":"https://github.com/cli/cli/blob/0eeec0b92edbe70199f9768522f831d3534f41ad/pkg/cmd/codespace/jupyter.go#L83-L106","documentation":"Reported by gh codespace jupyter when the port-forward tunnel to the codespace terminates with an error. The tunnel goroutine runs fwd.ForwardPortToListener(ctx, opts, listen) and sends its result on tunnelClosed; receiving an error there means the websocket tunnel relaying the local listener to the codespace port died or failed to start forwarding. It is distinct from ctx.Done() on the same select, which is the normal shutdown path.","triggerScenarios":"`gh codespace jupyter` running with the browser open, then the tunnel drops: codespace stopped or deleted mid-session, network interruption killing the websocket, relay server closing the connection, or ForwardPortToListener failing immediately (e.g. the codespace port not listening yet inside the container).","commonSituations":"Laptop sleep/resume or Wi-Fi change dropping the tunnel; the codespace auto-stopping after idle timeout; Jupyter server process dying inside the codespace; flaky corporate networks that idle-out long-lived websockets.","solutions":["Simply rerun `gh codespace jupyter` - transient tunnel drops are the most common cause","Check the codespace is still running: `gh codespace list` (state should be Running, not Shutdown/Deleted)","If drops are frequent on your network, keep the session active or adjust idle timeout: `gh codespace edit -m ... --idle-timeout` / org policy","Read the wrapped %w error: 'context canceled' during intentional shutdown is benign; websocket 1006/close errors point to network or relay issues"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Distinguish intentional shutdown from real tunnel failure\nselect {\ncase err := <-tunnelClosed:\n    if errors.Is(err, context.Canceled) { return nil } // normal shutdown\n    return fmt.Errorf(\"tunnel closed: %w\", err)\ncase <-ctx.Done():\n    return nil\n}","preventionTips":["Treat tunnel drops during long sessions as expected; re-run the command","Keep the codespace active (raise idle timeout) for long browser sessions","Avoid network switches/sleep while the jupyter session is open","Check codespace state after a drop before assuming client-side bugs"],"tags":["go","gh-cli","codespaces","tunnel","port-forwarding","network"],"backgroundTag":null,"analyzedSha":"0eeec0b92edbe70199f9768522f831d3534f41ad","analyzedAt":"2026-08-15T12:31:05.478Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}