Skip to content

Commit 1f8dcd3

Browse files
committed
fix(spec-044): env_kind prioritize cloud_ide over ci (gemini P1)
Gemini cross-review flagged that Codespaces + Gitpod routinely set CI=true alongside their own markers (CODESPACES, GITPOD_WORKSPACE_ID). With the original CI-wins ordering from design §4.2 / research.md R1, real humans working in ephemeral cloud IDEs were being classified as `ci`, artificially deflating Cloud IDE retention numbers and skewing the activation funnel. Reorder the decision tree so cloud_ide is checked before ci. Ordinary CI runners (without CODESPACES/GITPOD/etc.) still classify as `ci`. This intentionally deviates from the locked design doc; comment block updated to explain why and cite the cross-review finding. Tests: - Add cloud-ide-codespaces-beats-ci (CI=true + CODESPACES=true -> cloud_ide) - Add cloud-ide-gitpod-prebuild-beats-ci (CI=true + GITPOD_WORKSPACE_ID -> cloud_ide) - Existing ci-beats-container case still passes (no cloud-IDE markers). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent ae18298 commit 1f8dcd3

2 files changed

Lines changed: 38 additions & 5 deletions

File tree

internal/telemetry/env_kind.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,16 @@ func hasAnyEnv(env map[string]string, names []string) bool {
142142

143143
// DetectEnvKind is the pure classifier that decides env_kind + env_markers
144144
// from an injected environment map, filesystem prober, OS name, and TTY
145-
// checker. Ordering is authoritative: CI wins over cloud-IDE wins over
146-
// container. See design §4.2 / research.md R1.
145+
// checker. Ordering is authoritative: cloud_ide wins over CI wins over
146+
// container.
147+
//
148+
// NOTE: This ordering deviates from the original design doc (§4.2 / research.md
149+
// R1) which put CI first. The change is motivated by gemini P1 cross-review:
150+
// GitHub Codespaces and Gitpod routinely set CI=true alongside their cloud-IDE
151+
// markers (CODESPACES, GITPOD_WORKSPACE_ID). Real humans in those ephemeral
152+
// cloud sessions were being classified as `ci`, artificially deflating the
153+
// Cloud IDE retention funnel. Prioritising cloud_ide over CI keeps interactive
154+
// human sessions in the right bucket while still catching ordinary CI runners.
147155
//
148156
// Inputs:
149157
// - env: map of env-var name → value (caller builds this from os.Environ).
@@ -176,12 +184,14 @@ func DetectEnvKind(env map[string]string, fs FileProber, osName string, tty TTYC
176184
markers.IsContainer = true
177185
}
178186

179-
// Decision tree (first match wins).
187+
// Decision tree (first match wins). cloud_ide is checked BEFORE ci because
188+
// Codespaces / Gitpod set CI=true alongside their own markers — see
189+
// function doc comment.
180190
switch {
181-
case markers.HasCIEnv:
182-
return EnvKindCI, markers
183191
case markers.HasCloudIDEEnv:
184192
return EnvKindCloudIDE, markers
193+
case markers.HasCIEnv:
194+
return EnvKindCI, markers
185195
case markers.IsContainer:
186196
return EnvKindContainer, markers
187197
}

internal/telemetry/env_kind_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,29 @@ func TestDetectEnvKind_DecisionTree(t *testing.T) {
142142
wantKind: EnvKindCloudIDE,
143143
wantMarkers: EnvMarkers{HasCloudIDEEnv: true},
144144
},
145+
{
146+
// Gemini P1: GitHub Codespaces sets CI=true alongside CODESPACES.
147+
// Must resolve to cloud_ide, NOT ci, because it's a real human in
148+
// an interactive ephemeral session.
149+
name: "cloud-ide-codespaces-beats-ci",
150+
env: map[string]string{"CI": "true", "CODESPACES": "true"},
151+
fs: newFakeFS(),
152+
osName: "linux",
153+
tty: fakeTTY(false),
154+
wantKind: EnvKindCloudIDE,
155+
wantMarkers: EnvMarkers{HasCIEnv: true, HasCloudIDEEnv: true},
156+
},
157+
{
158+
// Gemini P1: Gitpod prebuilds run with CI=true but the workspace ID
159+
// is present — still a cloud_ide session, not a CI runner.
160+
name: "cloud-ide-gitpod-prebuild-beats-ci",
161+
env: map[string]string{"CI": "true", "GITPOD_WORKSPACE_ID": "abc"},
162+
fs: newFakeFS(),
163+
osName: "linux",
164+
tty: fakeTTY(false),
165+
wantKind: EnvKindCloudIDE,
166+
wantMarkers: EnvMarkers{HasCIEnv: true, HasCloudIDEEnv: true},
167+
},
145168
{
146169
name: "cloud-ide-gitpod",
147170
env: map[string]string{"GITPOD_WORKSPACE_ID": "abc"},

0 commit comments

Comments
 (0)