excludedTools;
private ProviderConfig provider;
+ private Boolean enableSessionTelemetry;
private PermissionHandler onPermissionRequest;
private UserInputHandler onUserInputRequest;
private SessionHooks hooks;
@@ -283,6 +284,38 @@ public SessionConfig setProvider(ProviderConfig provider) {
return this;
}
+ /**
+ * Enables or disables internal session telemetry for this session. When
+ * {@code false}, disables session telemetry. When {@code null} (the default) or
+ * {@code true}, telemetry is enabled for GitHub-authenticated sessions. When a
+ * custom {@link ProviderConfig} (BYOK) is configured, session telemetry is
+ * always disabled regardless of this setting. This is independent of
+ * {@link com.github.copilot.sdk.json.CopilotClientOptions#getTelemetry()
+ * CopilotClientOptions.TelemetryConfig}, which configures OpenTelemetry export
+ * for observability.
+ *
+ * @return whether session telemetry is enabled
+ */
+ public Boolean getEnableSessionTelemetry() {
+ return enableSessionTelemetry;
+ }
+
+ /**
+ * Enables or disables internal session telemetry for this session. When
+ * {@code false}, disables session telemetry. When {@code null} (the default) or
+ * {@code true}, telemetry is enabled for GitHub-authenticated sessions. When a
+ * custom {@link ProviderConfig} (BYOK) is configured, session telemetry is
+ * always disabled regardless of this setting.
+ *
+ * @param enableSessionTelemetry
+ * whether to enable session telemetry
+ * @return this config instance for method chaining
+ */
+ public SessionConfig setEnableSessionTelemetry(Boolean enableSessionTelemetry) {
+ this.enableSessionTelemetry = enableSessionTelemetry;
+ return this;
+ }
+
/**
* Gets the permission request handler.
*
@@ -835,6 +868,7 @@ public SessionConfig clone() {
copy.availableTools = this.availableTools != null ? new ArrayList<>(this.availableTools) : null;
copy.excludedTools = this.excludedTools != null ? new ArrayList<>(this.excludedTools) : null;
copy.provider = this.provider;
+ copy.enableSessionTelemetry = this.enableSessionTelemetry;
copy.onPermissionRequest = this.onPermissionRequest;
copy.onUserInputRequest = this.onUserInputRequest;
copy.hooks = this.hooks;
diff --git a/src/test/java/com/github/copilot/sdk/CapiProxy.java b/src/test/java/com/github/copilot/sdk/CapiProxy.java
index d91762d51..09c4e2016 100644
--- a/src/test/java/com/github/copilot/sdk/CapiProxy.java
+++ b/src/test/java/com/github/copilot/sdk/CapiProxy.java
@@ -98,6 +98,10 @@ public String start() throws IOException, InterruptedException {
: new ProcessBuilder("npx", "tsx", "server.ts");
pb.directory(harnessDir.toFile());
pb.redirectErrorStream(false);
+ // Tell the replaying proxy to fail fast on unmatched requests rather than
+ // forwarding them to the real API. Without this, unmatched requests hit the
+ // live API with a fake token and crash the proxy's JSON parser.
+ pb.environment().put("GITHUB_ACTIONS", "true");
process = pb.start();
diff --git a/src/test/java/com/github/copilot/sdk/CompactionTest.java b/src/test/java/com/github/copilot/sdk/CompactionTest.java
index da24dabd1..306eeb6c7 100644
--- a/src/test/java/com/github/copilot/sdk/CompactionTest.java
+++ b/src/test/java/com/github/copilot/sdk/CompactionTest.java
@@ -12,6 +12,7 @@
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
@@ -52,10 +53,17 @@ static void teardown() throws Exception {
/**
* Verifies that compaction is triggered with low threshold and emits events.
*
+ *
+ * Disabled due to flakiness — compaction timing is non-deterministic and the
+ * snapshot cannot reliably match across platforms. The reference implementation
+ * (nodejs) also skips this test. See copilot-sdk#1227.
+ *
* @see Snapshot:
* compaction/should_trigger_compaction_with_low_threshold_and_emit_events
*/
@Test
+ @Disabled("Flaky: compaction timing varies by platform — see https://github.com/github/copilot-sdk/issues/1227")
@Timeout(value = 300, unit = TimeUnit.SECONDS)
void testShouldTriggerCompactionWithLowThresholdAndEmitEvents() throws Exception {
ctx.configureForTest("compaction", "should_trigger_compaction_with_low_threshold_and_emit_events");
diff --git a/src/test/java/com/github/copilot/sdk/ConfigCloneTest.java b/src/test/java/com/github/copilot/sdk/ConfigCloneTest.java
index b060c278a..f7ce3aa4d 100644
--- a/src/test/java/com/github/copilot/sdk/ConfigCloneTest.java
+++ b/src/test/java/com/github/copilot/sdk/ConfigCloneTest.java
@@ -194,6 +194,44 @@ void messageOptionsCloneBasic() {
assertEquals(original.getMode(), cloned.getMode());
}
+ @Test
+ void sessionConfigEnableSessionTelemetryCopied() {
+ SessionConfig original = new SessionConfig();
+ original.setEnableSessionTelemetry(false);
+
+ SessionConfig cloned = original.clone();
+
+ assertFalse(cloned.getEnableSessionTelemetry());
+ }
+
+ @Test
+ void sessionConfigEnableSessionTelemetryDefaultIsNull() {
+ SessionConfig original = new SessionConfig();
+
+ SessionConfig cloned = original.clone();
+
+ assertNull(cloned.getEnableSessionTelemetry());
+ }
+
+ @Test
+ void resumeSessionConfigEnableSessionTelemetryCopied() {
+ ResumeSessionConfig original = new ResumeSessionConfig();
+ original.setEnableSessionTelemetry(false);
+
+ ResumeSessionConfig cloned = original.clone();
+
+ assertFalse(cloned.getEnableSessionTelemetry());
+ }
+
+ @Test
+ void resumeSessionConfigEnableSessionTelemetryDefaultIsNull() {
+ ResumeSessionConfig original = new ResumeSessionConfig();
+
+ ResumeSessionConfig cloned = original.clone();
+
+ assertNull(cloned.getEnableSessionTelemetry());
+ }
+
@Test
void clonePreservesNullFields() {
CopilotClientOptions opts = new CopilotClientOptions();
diff --git a/src/test/java/com/github/copilot/sdk/CopilotSessionTest.java b/src/test/java/com/github/copilot/sdk/CopilotSessionTest.java
index 8a78c0e4f..fc44880cf 100644
--- a/src/test/java/com/github/copilot/sdk/CopilotSessionTest.java
+++ b/src/test/java/com/github/copilot/sdk/CopilotSessionTest.java
@@ -20,6 +20,7 @@
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import com.github.copilot.sdk.generated.SessionEvent;
@@ -306,6 +307,7 @@ void testShouldResumeSessionUsingTheSameClient() throws Exception {
* @see Snapshot: session/should_resume_a_session_using_a_new_client
*/
@Test
+ @Tag("isolated-resume")
void testShouldResumeSessionUsingNewClient() throws Exception {
ctx.configureForTest("session", "should_resume_a_session_using_a_new_client");
diff --git a/src/test/java/com/github/copilot/sdk/E2ETestContext.java b/src/test/java/com/github/copilot/sdk/E2ETestContext.java
index 58e8400e3..9680148ff 100644
--- a/src/test/java/com/github/copilot/sdk/E2ETestContext.java
+++ b/src/test/java/com/github/copilot/sdk/E2ETestContext.java
@@ -270,15 +270,10 @@ public Map getEnvironment() {
env.put("REQUESTS_CA_BUNDLE", caFile);
env.put("CURL_CA_BUNDLE", caFile);
env.put("GIT_SSL_CAINFO", caFile);
- env.put("GH_TOKEN", "");
- env.put("GITHUB_TOKEN", "");
- env.put("GH_ENTERPRISE_TOKEN", "");
- env.put("GITHUB_ENTERPRISE_TOKEN", "");
- }
-
- if ("true".equals(System.getenv("GITHUB_ACTIONS"))) {
env.put("GH_TOKEN", "fake-token-for-e2e-tests");
env.put("GITHUB_TOKEN", "fake-token-for-e2e-tests");
+ env.put("GH_ENTERPRISE_TOKEN", "");
+ env.put("GITHUB_ENTERPRISE_TOKEN", "");
}
return env;
@@ -291,13 +286,7 @@ public Map getEnvironment() {
*/
public CopilotClient createClient() {
CopilotClientOptions options = new CopilotClientOptions().setCliPath(cliPath).setCwd(workDir.toString())
- .setEnvironment(getEnvironment());
-
- // In CI (GitHub Actions), use a fake token to avoid auth issues
- String ci = System.getenv("GITHUB_ACTIONS");
- if (ci != null && !ci.isEmpty()) {
- options.setGitHubToken("fake-token-for-e2e-tests");
- }
+ .setEnvironment(getEnvironment()).setGitHubToken("fake-token-for-e2e-tests");
return new CopilotClient(options);
}
@@ -321,10 +310,7 @@ public CopilotClient createClient(CopilotClientOptions options) {
if (options.getEnvironment() == null || options.getEnvironment().isEmpty()) {
options.setEnvironment(getEnvironment());
}
-
- // In CI (GitHub Actions), use a fake token to avoid auth issues
- String ci = System.getenv("GITHUB_ACTIONS");
- if (ci != null && !ci.isEmpty() && options.getGitHubToken() == null) {
+ if (options.getGitHubToken() == null) {
options.setGitHubToken("fake-token-for-e2e-tests");
}
diff --git a/src/test/java/com/github/copilot/sdk/ExecutorWiringTest.java b/src/test/java/com/github/copilot/sdk/ExecutorWiringTest.java
index f836be89a..a5eb3a62d 100644
--- a/src/test/java/com/github/copilot/sdk/ExecutorWiringTest.java
+++ b/src/test/java/com/github/copilot/sdk/ExecutorWiringTest.java
@@ -87,12 +87,8 @@ int getTaskCount() {
private CopilotClientOptions createOptionsWithExecutor(TrackingExecutor executor) {
CopilotClientOptions options = new CopilotClientOptions().setCliPath(ctx.getCliPath())
- .setCwd(ctx.getWorkDir().toString()).setEnvironment(ctx.getEnvironment()).setExecutor(executor);
-
- String ci = System.getenv("GITHUB_ACTIONS");
- if (ci != null && !ci.isEmpty()) {
- options.setGitHubToken("fake-token-for-e2e-tests");
- }
+ .setCwd(ctx.getWorkDir().toString()).setEnvironment(ctx.getEnvironment()).setExecutor(executor)
+ .setGitHubToken("fake-token-for-e2e-tests");
return options;
}
diff --git a/src/test/java/com/github/copilot/sdk/SessionEventDeserializationTest.java b/src/test/java/com/github/copilot/sdk/SessionEventDeserializationTest.java
index 5a9362f8d..109144e00 100644
--- a/src/test/java/com/github/copilot/sdk/SessionEventDeserializationTest.java
+++ b/src/test/java/com/github/copilot/sdk/SessionEventDeserializationTest.java
@@ -736,7 +736,7 @@ void testParseAbortEvent() throws Exception {
{
"type": "abort",
"data": {
- "reason": "user_requested"
+ "reason": "user_initiated"
}
}
""";
@@ -1987,14 +1987,14 @@ void testAbortEventAllFields() throws Exception {
{
"type": "abort",
"data": {
- "reason": "user_cancelled"
+ "reason": "user_abort"
}
}
""";
var event = (AbortEvent) parseJson(json);
assertNotNull(event);
- assertEquals("user_cancelled", event.getData().reason());
+ assertEquals(AbortReason.USER_ABORT, event.getData().reason());
}
@Test
diff --git a/src/test/java/com/github/copilot/sdk/SessionRequestBuilderTest.java b/src/test/java/com/github/copilot/sdk/SessionRequestBuilderTest.java
index a79c02334..0d13576eb 100644
--- a/src/test/java/com/github/copilot/sdk/SessionRequestBuilderTest.java
+++ b/src/test/java/com/github/copilot/sdk/SessionRequestBuilderTest.java
@@ -86,6 +86,20 @@ void testBuildCreateRequestSetsClientName() {
assertEquals("my-app", request.getClientName());
}
+ @Test
+ void testBuildCreateRequestForwardsEnableSessionTelemetryWhenFalse() {
+ var config = new SessionConfig().setEnableSessionTelemetry(false);
+ CreateSessionRequest request = SessionRequestBuilder.buildCreateRequest(config);
+ assertFalse(request.getEnableSessionTelemetry());
+ }
+
+ @Test
+ void testBuildCreateRequestOmitsEnableSessionTelemetryWhenNotSet() {
+ var config = new SessionConfig();
+ CreateSessionRequest request = SessionRequestBuilder.buildCreateRequest(config);
+ assertNull(request.getEnableSessionTelemetry());
+ }
+
// =========================================================================
// buildResumeRequest
// =========================================================================
@@ -99,6 +113,20 @@ void testBuildResumeRequestNullConfig() {
assertEquals("direct", request.getEnvValueMode(), "envValueMode should be 'direct' even for null config");
}
+ @Test
+ void testBuildResumeRequestForwardsEnableSessionTelemetryWhenFalse() {
+ var config = new ResumeSessionConfig().setEnableSessionTelemetry(false);
+ ResumeSessionRequest request = SessionRequestBuilder.buildResumeRequest("sid-1", config);
+ assertFalse(request.getEnableSessionTelemetry());
+ }
+
+ @Test
+ void testBuildResumeRequestOmitsEnableSessionTelemetryWhenNotSet() {
+ var config = new ResumeSessionConfig();
+ ResumeSessionRequest request = SessionRequestBuilder.buildResumeRequest("sid-1", config);
+ assertNull(request.getEnableSessionTelemetry());
+ }
+
@Test
void testBuildResumeRequestWithTools() {
var tool = ToolDefinition.create("my_tool", "A tool", Map.of("type", "object"),
@@ -465,4 +493,46 @@ void testBuildResumeRequestPropagatesInstructionDirectories() {
assertEquals(dirs, request.getInstructionDirectories());
}
+
+ // =========================================================================
+ // enableSessionTelemetry serialization
+ // =========================================================================
+
+ @Test
+ void testCreateRequestSerializesEnableSessionTelemetryWhenFalse() throws Exception {
+ var config = new SessionConfig().setEnableSessionTelemetry(false);
+ CreateSessionRequest request = SessionRequestBuilder.buildCreateRequest(config);
+ var mapper = JsonRpcClient.getObjectMapper();
+ var json = mapper.writeValueAsString(request);
+ assertTrue(json.contains("\"enableSessionTelemetry\":false"),
+ "enableSessionTelemetry should be serialized when set to false");
+ }
+
+ @Test
+ void testCreateRequestOmitsEnableSessionTelemetryWhenNull() throws Exception {
+ var config = new SessionConfig();
+ CreateSessionRequest request = SessionRequestBuilder.buildCreateRequest(config);
+ var mapper = JsonRpcClient.getObjectMapper();
+ var json = mapper.writeValueAsString(request);
+ assertFalse(json.contains("enableSessionTelemetry"), "enableSessionTelemetry should be omitted when null");
+ }
+
+ @Test
+ void testResumeRequestSerializesEnableSessionTelemetryWhenFalse() throws Exception {
+ var config = new ResumeSessionConfig().setEnableSessionTelemetry(false);
+ ResumeSessionRequest request = SessionRequestBuilder.buildResumeRequest("sid-tel", config);
+ var mapper = JsonRpcClient.getObjectMapper();
+ var json = mapper.writeValueAsString(request);
+ assertTrue(json.contains("\"enableSessionTelemetry\":false"),
+ "enableSessionTelemetry should be serialized when set to false");
+ }
+
+ @Test
+ void testResumeRequestOmitsEnableSessionTelemetryWhenNull() throws Exception {
+ var config = new ResumeSessionConfig();
+ ResumeSessionRequest request = SessionRequestBuilder.buildResumeRequest("sid-tel", config);
+ var mapper = JsonRpcClient.getObjectMapper();
+ var json = mapper.writeValueAsString(request);
+ assertFalse(json.contains("enableSessionTelemetry"), "enableSessionTelemetry should be omitted when null");
+ }
}
diff --git a/src/test/java/com/github/copilot/sdk/StreamingFidelityTest.java b/src/test/java/com/github/copilot/sdk/StreamingFidelityTest.java
index e922f4bf0..d3df63eb0 100644
--- a/src/test/java/com/github/copilot/sdk/StreamingFidelityTest.java
+++ b/src/test/java/com/github/copilot/sdk/StreamingFidelityTest.java
@@ -14,6 +14,7 @@
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import com.github.copilot.sdk.generated.SessionEvent;
@@ -139,6 +140,7 @@ void testShouldNotProduceDeltasWhenStreamingIsDisabled() throws Exception {
* @see Snapshot: streaming_fidelity/should_produce_deltas_after_session_resume
*/
@Test
+ @Tag("isolated-resume")
void testShouldProduceDeltasAfterSessionResume() throws Exception {
ctx.configureForTest("streaming_fidelity", "should_produce_deltas_after_session_resume");
@@ -192,6 +194,7 @@ void testShouldProduceDeltasAfterSessionResume() throws Exception {
* streaming_fidelity/should_not_produce_deltas_after_session_resume_with_streaming_disabled
*/
@Test
+ @Tag("isolated-resume")
void testShouldNotProduceDeltasAfterSessionResumeWithStreamingDisabled() throws Exception {
ctx.configureForTest("streaming_fidelity",
"should_not_produce_deltas_after_session_resume_with_streaming_disabled");