Skip to content

Commit 6ef943d

Browse files
committed
test: comprehensive Codespace e2e tests, fix detection for GH Actions + GH_TOKEN
- Add 32 adversarial e2e tests covering: - Full Codespace env simulation (all 11 official env vars) - GitHub Actions environment detection - `GH_TOKEN` (gh CLI alias) as machine-scoped token - Token prefix variations (`ghu_`, `ghs_`, `ghp_`, `github_pat_`) - `CODESPACES=false` and `CODESPACES=""` edge cases - Config-based override still works in Codespaces - Other providers (anthropic, openai) unaffected - Retry delay monotonicity and bounds - Malformed JSON error handling in retryable() - Extend detection to GitHub Actions (`GITHUB_ACTIONS=true`) - Treat `GH_TOKEN` as machine-scoped alongside `GITHUB_TOKEN` - Add warn log when max retries exhausted (self-heal guidance) - Revert discover.ts comment to upstream original (only user-facing message in index.ts was the real fix)
1 parent ace2ada commit 6ef943d

4 files changed

Lines changed: 510 additions & 14 deletions

File tree

packages/opencode/src/mcp/discover.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ async function discoverClaudeCode(
165165
*
166166
* Security model: Project-scoped servers (.vscode/mcp.json, .mcp.json, etc.) are
167167
* discovered with enabled=false so they don't auto-connect. Users must explicitly
168-
* approve them by asking the assistant to add them. Home-directory configs (~/.claude.json,
168+
* approve them via /discover-and-add-mcps. Home-directory configs (~/.claude.json,
169169
* ~/.gemini/settings.json) are auto-enabled since they're user-owned.
170170
*
171171
* Searches both the project directory and the home directory.

packages/opencode/src/provider/provider.ts

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1071,23 +1071,37 @@ export namespace Provider {
10711071

10721072
// load env
10731073
const env = Env.all()
1074-
// altimate_change start — skip github-models/github-copilot auto-enable from GITHUB_TOKEN in Codespaces
1075-
// In GitHub Codespaces, GITHUB_TOKEN is a machine-scoped token for repo operations,
1076-
// not for model inference. Auto-enabling these providers leads to immediate rate limiting
1077-
// with "Too Many Requests" errors and long retry loops.
1078-
const isCodespace = Boolean(env["CODESPACES"] || env["GITHUB_CODESPACE_TOKEN"])
1079-
const codespaceSkipProviders = new Set(["github-models", "github-copilot"])
1074+
// altimate_change start — skip github-models/github-copilot auto-enable from machine-scoped GITHUB_TOKEN
1075+
// In GitHub Codespaces and GitHub Actions, GITHUB_TOKEN is a machine-scoped
1076+
// token for repo operations, not for model inference. Auto-enabling these
1077+
// providers leads to immediate rate limiting ("Too Many Requests") and long
1078+
// retry loops. We detect these environments and skip auto-enable unless the
1079+
// user has explicitly set a different token.
1080+
//
1081+
// Environment detection (official GitHub docs):
1082+
// Codespaces: CODESPACES=true, CODESPACE_NAME set
1083+
// Actions: GITHUB_ACTIONS=true, CI=true
1084+
//
1085+
// Machine-scoped tokens to ignore: GITHUB_TOKEN and GH_TOKEN (gh CLI alias)
1086+
const isMachineEnv = Boolean(env["CODESPACES"] || env["GITHUB_ACTIONS"])
1087+
const machineTokenNames = new Set(["GITHUB_TOKEN", "GH_TOKEN"])
1088+
const skipGithubProviders = new Set(["github-models", "github-copilot"])
10801089
// altimate_change end
10811090
for (const [id, provider] of Object.entries(database)) {
10821091
const providerID = ProviderID.make(id)
10831092
if (disabled.has(providerID)) continue
10841093
const apiKey = provider.env.map((item) => env[item]).find(Boolean)
10851094
if (!apiKey) continue
1086-
// altimate_change start — skip providers that use GITHUB_TOKEN in Codespaces
1087-
if (isCodespace && codespaceSkipProviders.has(id) && provider.env.includes("GITHUB_TOKEN")) {
1088-
const hasExplicitKey = provider.env.some((item) => item !== "GITHUB_TOKEN" && env[item])
1089-
if (!hasExplicitKey) {
1090-
log.info("skipping provider in Codespace (GITHUB_TOKEN is not for model inference)", { providerID })
1095+
// altimate_change start — skip GitHub providers when only machine-scoped tokens exist
1096+
if (isMachineEnv && skipGithubProviders.has(id)) {
1097+
// Check if ALL env vars for this provider are machine-scoped tokens
1098+
const matchedEnvVars = provider.env.filter((item) => env[item])
1099+
const allMachineScoped = matchedEnvVars.every((item) => machineTokenNames.has(item))
1100+
if (allMachineScoped) {
1101+
log.info("skipping provider in machine environment (token is not for model inference)", {
1102+
providerID,
1103+
environment: env["CODESPACES"] ? "codespace" : "github-actions",
1104+
})
10911105
continue
10921106
}
10931107
}

packages/opencode/src/session/processor.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -394,9 +394,9 @@ export namespace SessionProcessor {
394394
})
395395
} else {
396396
const retry = SessionRetry.retryable(error)
397-
// altimate_change start — cap retries to avoid infinite retry loops (e.g. GitHub Models rate limits in Codespaces)
397+
// altimate_change start — cap retries to avoid infinite loops, log on exhaustion
398398
if (retry !== undefined && attempt < SessionRetry.RETRY_MAX_ATTEMPTS) {
399-
// altimate_change end
399+
// altimate_change end
400400
attempt++
401401
const delay = SessionRetry.delay(attempt, error.name === "APIError" ? error : undefined)
402402
SessionStatus.set(input.sessionID, {
@@ -408,6 +408,16 @@ export namespace SessionProcessor {
408408
await SessionRetry.sleep(delay, input.abort).catch(() => {})
409409
continue
410410
}
411+
// altimate_change start — log when retries exhausted for debugging
412+
if (retry !== undefined) {
413+
log.warn("max retry attempts reached, giving up", {
414+
attempt,
415+
message: retry,
416+
providerID: input.model.providerID,
417+
modelID: input.model.id,
418+
})
419+
}
420+
// altimate_change end
411421
input.assistantMessage.error = error
412422
Bus.publish(Session.Event.Error, {
413423
sessionID: input.assistantMessage.sessionID,

0 commit comments

Comments
 (0)