diff --git a/docs/getting-started.md b/docs/getting-started.md
index 2c4e6159..4fc132ff 100644
--- a/docs/getting-started.md
+++ b/docs/getting-started.md
@@ -1048,6 +1048,18 @@ await using var session = await client.CreateSessionAsync();
---
+## Troubleshooting Connection Issues
+
+If you encounter errors like "The JSON-RPC connection with the remote party was lost before the request could complete", see our [Troubleshooting Connection Errors guide](./troubleshooting-connection-errors.md) for detailed diagnostics and solutions.
+
+Common issues include:
+- CLI not installed or not in PATH
+- Not authenticated with GitHub (`copilot auth login`)
+- Version incompatibility between SDK and CLI
+- Process permissions or antivirus interference
+
+---
+
## Learn More
- [Node.js SDK Reference](../nodejs/README.md)
diff --git a/docs/troubleshooting-connection-errors.md b/docs/troubleshooting-connection-errors.md
new file mode 100644
index 00000000..b4a0ba33
--- /dev/null
+++ b/docs/troubleshooting-connection-errors.md
@@ -0,0 +1,221 @@
+# Troubleshooting Connection Errors
+
+This guide helps you diagnose and fix common connection errors when using the GitHub Copilot SDK.
+
+## "The JSON-RPC connection with the remote party was lost before the request could complete"
+
+This error occurs when the SDK cannot communicate with the Copilot CLI. Here are the most common causes and solutions:
+
+### 1. CLI Not Installed or Not in PATH
+
+**Symptom**: Error message mentions "Failed to start Copilot CLI process"
+
+**Solution**:
+- Verify the CLI is installed: `copilot --version`
+- If not installed, follow the [installation guide](https://docs.github.com/en/copilot/how-tos/set-up/install-copilot-cli)
+- If installed but not in PATH, specify the full path in your code:
+
+
+.NET
+
+```csharp
+var client = new CopilotClient(new CopilotClientOptions
+{
+ CliPath = "/path/to/copilot" // or "C:\\path\\to\\copilot.exe" on Windows
+});
+```
+
+
+
+
+Node.js
+
+```typescript
+const client = new CopilotClient({
+ cliPath: "/path/to/copilot"
+});
+```
+
+
+
+
+Python
+
+```python
+client = CopilotClient({
+ "cli_path": "/path/to/copilot"
+})
+```
+
+
+
+### 2. CLI Process Exits Immediately
+
+**Symptom**: Error message mentions "CLI process exited immediately with code X" or "CLI process exited unexpectedly"
+
+**Common causes**:
+- **Not authenticated**: The CLI requires authentication with GitHub
+ - Solution: Run `copilot auth login` to authenticate
+ - Verify authentication: `copilot auth status`
+
+- **Missing dependencies**: The CLI may require Node.js or other dependencies
+ - For JavaScript-based CLI: Ensure Node.js 18+ is installed
+ - Check the error output included in the exception message for clues
+
+- **Permissions issues**: The CLI executable may not have execute permissions
+ - On Unix/Linux/Mac: `chmod +x /path/to/copilot`
+
+### 3. Version Incompatibility
+
+**Symptom**: Error mentioning "protocol version mismatch"
+
+**Solution**: Update either the SDK or CLI to compatible versions
+- Check the [release notes](https://github.com/github/copilot-sdk/releases) for compatibility information
+- Update CLI: Follow the installation guide to get the latest version
+- Update SDK: Install the latest SDK package
+
+### 4. Port Already in Use (TCP Mode)
+
+**Symptom**: Connection error when using TCP mode
+
+**Solution**:
+- Let the SDK choose a random port (don't specify the port option)
+- Or specify a different port:
+
+```csharp
+var client = new CopilotClient(new CopilotClientOptions
+{
+ UseStdio = false,
+ Port = 8080 // Choose an available port
+});
+```
+
+### 5. Timeout Waiting for CLI
+
+**Symptom**: "Timed out waiting for CLI server to announce its port"
+
+**Causes**:
+- CLI is taking too long to start (slow machine, antivirus scanning, etc.)
+- CLI failed to start but didn't exit
+- Firewall blocking network communication
+
+**Solutions**:
+- Check if antivirus is scanning the CLI executable
+- Try using stdio mode instead of TCP (default in SDK):
+ ```csharp
+ var client = new CopilotClient(new CopilotClientOptions { UseStdio = true });
+ ```
+- Check firewall settings if using TCP mode
+
+## Getting More Information
+
+### Enable Debug Logging
+
+To see detailed diagnostic information:
+
+
+.NET
+
+```csharp
+using Microsoft.Extensions.Logging;
+
+var loggerFactory = LoggerFactory.Create(builder =>
+{
+ builder.AddConsole();
+ builder.SetMinimumLevel(LogLevel.Debug);
+});
+
+var client = new CopilotClient(new CopilotClientOptions
+{
+ Logger = loggerFactory.CreateLogger(),
+ LogLevel = "debug" // CLI log level
+});
+```
+
+
+
+
+Node.js
+
+```typescript
+const client = new CopilotClient({
+ logLevel: "debug"
+});
+```
+
+
+
+
+Python
+
+```python
+import logging
+logging.basicConfig(level=logging.DEBUG)
+
+client = CopilotClient({
+ "log_level": "debug"
+})
+```
+
+
+
+### Check CLI Directly
+
+Test the CLI independently to isolate SDK issues:
+
+```bash
+# Test basic CLI functionality
+copilot --version
+
+# Check authentication
+copilot auth status
+
+# Start CLI in server mode manually
+copilot --server --port 4321
+
+# In another terminal, try to connect using the SDK
+# with cliUrl: "localhost:4321"
+```
+
+### Examine Error Output
+
+The latest versions of the SDK include stderr output from the CLI in error messages when processes fail. Look for:
+- Authentication errors
+- Missing file or permission errors
+- Node.js errors (if CLI is JS-based)
+- Network/proxy configuration issues
+
+## Common Error Messages and Solutions
+
+| Error Message | Likely Cause | Solution |
+|--------------|--------------|----------|
+| "Failed to start Copilot CLI process" | CLI not found or not executable | Check installation and PATH |
+| "exited immediately with code 1" | Authentication or configuration error | Run `copilot auth login` |
+| "exited immediately with code 127" | Command not found | Verify CLI is in PATH |
+| "Timed out waiting for CLI server" | CLI failed to start or network issue | Check logs, try stdio mode |
+| "protocol version mismatch" | SDK and CLI versions incompatible | Update SDK or CLI |
+
+## Still Having Issues?
+
+If you're still experiencing problems:
+
+1. **Collect diagnostic information**:
+ - SDK version
+ - CLI version (`copilot --version`)
+ - Operating system and version
+ - Full error message with stack trace
+ - CLI stderr output (included in recent error messages)
+
+2. **Create a minimal reproduction**:
+ - Simplest possible code that reproduces the error
+ - Share your client configuration options
+
+3. **Report the issue**:
+ - Open an issue on the [GitHub repository](https://github.com/github/copilot-sdk)
+ - Include all diagnostic information collected above
+
+## Related Documentation
+
+- [Getting Started Guide](./getting-started.md)
+- [API Reference](../dotnet/README.md)
+- [Copilot CLI Documentation](https://docs.github.com/en/copilot/how-tos/set-up/install-copilot-cli)
diff --git a/dotnet/src/Client.cs b/dotnet/src/Client.cs
index 112e988e..876be1fa 100644
--- a/dotnet/src/Client.cs
+++ b/dotnet/src/Client.cs
@@ -596,6 +596,14 @@ internal static async Task InvokeRpcAsync(JsonRpc rpc, string method, obje
{
return await rpc.InvokeWithCancellationAsync(method, args, cancellationToken);
}
+ catch (StreamJsonRpc.ConnectionLostException ex)
+ {
+ throw new IOException(
+ $"Communication error with Copilot CLI: The connection was lost. " +
+ $"This usually means the CLI process crashed or exited unexpectedly. " +
+ $"See the troubleshooting guide at https://github.com/github/copilot-sdk/blob/main/docs/troubleshooting-connection-errors.md for help. " +
+ $"Details: {ex.Message}", ex);
+ }
catch (StreamJsonRpc.RemoteRpcException ex)
{
throw new IOException($"Communication error with Copilot CLI: {ex.Message}", ex);
@@ -683,21 +691,72 @@ private async Task VerifyProtocolVersionAsync(Connection connection, Cancellatio
startInfo.Environment.Remove("NODE_DEBUG");
var cliProcess = new Process { StartInfo = startInfo };
- cliProcess.Start();
+
+ try
+ {
+ cliProcess.Start();
+ }
+ catch (Exception ex)
+ {
+ throw new InvalidOperationException(
+ $"Failed to start Copilot CLI process. " +
+ $"Please ensure the Copilot CLI is installed and accessible at '{cliPath}'. " +
+ $"Error: {ex.Message}", ex);
+ }
- // Forward stderr to logger
- _ = Task.Run(async () =>
+ // Start capturing stderr immediately to avoid race conditions
+ var stderrLines = new List();
+ var stderrLock = new object();
+ var stderrTask = Task.Run(async () =>
{
- while (cliProcess != null && !cliProcess.HasExited)
+ try
{
- var line = await cliProcess.StandardError.ReadLineAsync(cancellationToken);
- if (line != null)
+ while (!cliProcess.HasExited)
{
- logger.LogDebug("[CLI] {Line}", line);
+ var line = await cliProcess.StandardError.ReadLineAsync(cancellationToken);
+ if (line != null)
+ {
+ lock (stderrLock)
+ {
+ stderrLines.Add(line);
+ // Keep only last 50 lines to avoid unbounded growth
+ if (stderrLines.Count > 50)
+ {
+ stderrLines.RemoveAt(0);
+ }
+ }
+ logger.LogDebug("[CLI] {Line}", line);
+ }
}
}
+ catch (Exception ex) when (ex is not OperationCanceledException)
+ {
+ logger.LogDebug("Error reading CLI stderr: {Error}", ex.Message);
+ }
}, cancellationToken);
+ // Check if process exited immediately (indicates a startup failure)
+ // Use a shorter delay and check HasExited to minimize false positives
+ await Task.Delay(50, cancellationToken);
+ if (cliProcess.HasExited)
+ {
+ var exitCode = cliProcess.ExitCode;
+
+ // Give a moment for stderr task to capture any error output
+ await Task.Delay(50, cancellationToken);
+
+ string errorOutput;
+ lock (stderrLock)
+ {
+ errorOutput = stderrLines.Count > 0 ? string.Join(Environment.NewLine, stderrLines) : string.Empty;
+ }
+
+ throw new InvalidOperationException(
+ $"Copilot CLI process exited immediately with code {exitCode}. " +
+ $"This may indicate the CLI is not properly installed or configured. " +
+ (string.IsNullOrEmpty(errorOutput) ? "Run with debug logging enabled for more details." : $"Error output:{Environment.NewLine}{errorOutput}"));
+ }
+
var detectedLocalhostTcpPort = (int?)null;
if (!options.UseStdio)
{
@@ -705,18 +764,45 @@ private async Task VerifyProtocolVersionAsync(Connection connection, Cancellatio
using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
cts.CancelAfter(TimeSpan.FromSeconds(30));
- while (!cts.Token.IsCancellationRequested)
+ try
{
- var line = await cliProcess.StandardOutput.ReadLineAsync(cts.Token);
- if (line == null) throw new Exception("CLI process exited unexpectedly");
-
- var match = Regex.Match(line, @"listening on port (\d+)", RegexOptions.IgnoreCase);
- if (match.Success)
+ while (!cts.Token.IsCancellationRequested)
{
- detectedLocalhostTcpPort = int.Parse(match.Groups[1].Value);
- break;
+ var line = await cliProcess.StandardOutput.ReadLineAsync(cts.Token);
+ if (line == null)
+ {
+ var exitCode = cliProcess.HasExited ? cliProcess.ExitCode : -1;
+
+ // Give a moment for stderr to be captured
+ await Task.Delay(50, CancellationToken.None);
+
+ string errorOutput;
+ lock (stderrLock)
+ {
+ errorOutput = stderrLines.Count > 0 ? string.Join(Environment.NewLine, stderrLines) : string.Empty;
+ }
+
+ throw new InvalidOperationException(
+ $"CLI process exited unexpectedly" +
+ (cliProcess.HasExited ? $" with code {exitCode}" : "") +
+ ". " +
+ (string.IsNullOrEmpty(errorOutput) ? "Check CLI logs for details." : $"Error output:{Environment.NewLine}{errorOutput}"));
+ }
+
+ var match = Regex.Match(line, @"listening on port (\d+)", RegexOptions.IgnoreCase);
+ if (match.Success)
+ {
+ detectedLocalhostTcpPort = int.Parse(match.Groups[1].Value);
+ break;
+ }
}
}
+ catch (OperationCanceledException)
+ {
+ throw new TimeoutException(
+ "Timed out waiting for CLI server to announce its port. " +
+ "The CLI may have failed to start or is not responding.");
+ }
}
return (cliProcess, detectedLocalhostTcpPort);
@@ -750,6 +836,14 @@ private async Task ConnectToServerAsync(Process? cliProcess, string?
if (_options.UseStdio)
{
if (cliProcess == null) throw new InvalidOperationException("CLI process not started");
+
+ // Check if process has already exited
+ if (cliProcess.HasExited)
+ {
+ throw new InvalidOperationException(
+ $"Cannot connect to CLI process - it has already exited with code {cliProcess.ExitCode}.");
+ }
+
inputStream = cliProcess.StandardOutput.BaseStream;
outputStream = cliProcess.StandardInput.BaseStream;
}
diff --git a/dotnet/test/ErrorHandlingTests.cs b/dotnet/test/ErrorHandlingTests.cs
new file mode 100644
index 00000000..7f850582
--- /dev/null
+++ b/dotnet/test/ErrorHandlingTests.cs
@@ -0,0 +1,88 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+using GitHub.Copilot.SDK;
+using Microsoft.Extensions.Logging;
+using Xunit;
+
+namespace GitHub.Copilot.SDK.Test;
+
+///
+/// Tests for error handling and diagnostics improvements.
+///
+public class ErrorHandlingTests
+{
+ [Fact]
+ public async Task Should_Provide_Helpful_Error_When_CLI_Not_Found()
+ {
+ // Arrange: Use a non-existent CLI path
+ var client = new CopilotClient(new CopilotClientOptions
+ {
+ CliPath = "/nonexistent/path/to/copilot",
+ AutoStart = true
+ });
+
+ // Act & Assert: Should throw with helpful error message
+ var exception = await Assert.ThrowsAsync(
+ async () => await client.CreateSessionAsync());
+
+ Assert.Contains("Failed to start Copilot CLI process", exception.Message);
+ Assert.Contains("Please ensure the Copilot CLI is installed", exception.Message);
+ }
+
+ [Fact]
+ public async Task Should_Detect_Immediate_Process_Exit()
+ {
+ // Arrange: Use an executable that exits immediately
+ // Platform-specific command that exits with non-zero code
+ string exitCommand;
+ if (OperatingSystem.IsWindows())
+ {
+ exitCommand = "cmd";
+ }
+ else
+ {
+ exitCommand = "false"; // Unix command that exits immediately with code 1
+ }
+
+ var clientOptions = new CopilotClientOptions
+ {
+ AutoStart = true
+ };
+
+ if (OperatingSystem.IsWindows())
+ {
+ clientOptions.CliPath = exitCommand;
+ clientOptions.CliArgs = ["/c", "exit", "1"]; // Exit with code 1
+ }
+ else
+ {
+ clientOptions.CliPath = exitCommand;
+ }
+
+ var client = new CopilotClient(clientOptions);
+
+ // Act & Assert: Should detect the immediate exit
+ var exception = await Assert.ThrowsAsync(
+ async () => await client.CreateSessionAsync());
+
+ // Should mention the process exited immediately
+ Assert.Contains("exited immediately", exception.Message);
+ }
+
+ [Fact]
+ public async Task Should_Provide_Clear_Error_For_Connection_Issues()
+ {
+ // Verify that ConnectionLostException is handled and wrapped properly
+ // by checking that the InvokeRpcAsync method has proper exception handling
+
+ // This is a minimal test that verifies the structure exists
+ // More comprehensive testing would require integration tests with a real CLI
+ var method = typeof(CopilotClient).GetMethod(
+ "InvokeRpcAsync",
+ System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
+
+ Assert.NotNull(method);
+ }
+}