Skip to content

Commit 59f5a8e

Browse files
committed
fix: Codespaces — skip GITHUB_TOKEN auto-enable for github-models, cap retries, fix phantom command
Closes #413 - Skip auto-enabling `github-models` and `github-copilot` providers when `CODESPACES=true` and only `GITHUB_TOKEN` is available (the Codespace machine token lacks model-inference permissions, causing immediate 429s) - Cap retry attempts at 5 (`RETRY_MAX_ATTEMPTS`) to prevent infinite retry loops with 30+ minute backoff - Replace phantom `/discover-and-add-mcps` toast with actionable message ("Ask the assistant to add them") - Add `.devcontainer/` config (Node 22, Bun 1.3.10) for reproducible Codespace environments - Update docs to reference `mcp_discover` tool instead of non-existent slash command
1 parent 3b6d5d4 commit 59f5a8e

11 files changed

Lines changed: 140 additions & 6 deletions

File tree

.devcontainer/devcontainer.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"name": "Altimate Code",
3+
"image": "mcr.microsoft.com/devcontainers/javascript-node:22",
4+
"features": {
5+
"ghcr.io/devcontainers/features/git:1": {},
6+
"ghcr.io/shyim/devcontainers-features/bun:0": {
7+
"version": "1.3.10"
8+
}
9+
},
10+
"postCreateCommand": ".devcontainer/post-create.sh",
11+
"customizations": {
12+
"vscode": {
13+
"extensions": [
14+
"biomejs.biome",
15+
"dbaeumer.vscode-eslint"
16+
],
17+
"settings": {
18+
"terminal.integrated.defaultProfile.linux": "bash"
19+
}
20+
},
21+
"codespaces": {
22+
"openFiles": [
23+
"README.md"
24+
]
25+
}
26+
},
27+
"forwardPorts": [],
28+
"remoteEnv": {
29+
"BUN_INSTALL": "/home/node/.bun",
30+
"PATH": "/home/node/.bun/bin:${containerEnv:PATH}"
31+
}
32+
}

.devcontainer/post-create.sh

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/bin/bash
2+
set -e
3+
4+
echo "=== Altimate Code: Codespace Setup ==="
5+
6+
# Configure git (required for tests)
7+
git config --global user.name "${GITHUB_USER:-codespace}"
8+
git config --global user.email "${GITHUB_USER:-codespace}@users.noreply.github.com"
9+
10+
# Install dependencies
11+
echo "Installing dependencies with Bun..."
12+
bun install
13+
14+
echo ""
15+
echo "=== Setup complete! ==="
16+
echo ""
17+
echo "Quick start:"
18+
echo " bun run build # Build the CLI"
19+
echo " bun test # Run tests"
20+
echo " bun turbo typecheck # Type-check all packages"
21+
echo ""
22+
echo "To install altimate globally after building:"
23+
echo " bun link"
24+
echo ""

docs/docs/configure/tools.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,10 +131,10 @@ The `mcp_discover` tool finds MCP servers configured in other AI coding tools an
131131
- `mcp_discover(action: "add", scope: "project")` — Write new servers to `.altimate-code/altimate-code.json`
132132
- `mcp_discover(action: "add", scope: "global")` — Write to the global config dir (`~/.config/opencode/`)
133133

134-
**Auto-discovery:** At startup, altimate-code discovers external MCP servers and shows a toast notification. Servers from your home directory (`~/.claude.json`, `~/.gemini/settings.json`) are auto-enabled since they're user-owned. Servers from project-level files (`.vscode/mcp.json`, `.mcp.json`, `.cursor/mcp.json`) are discovered but **disabled by default** for security — run `/discover-and-add-mcps` to review and enable them.
134+
**Auto-discovery:** At startup, altimate-code discovers external MCP servers and shows a toast notification. Servers from your home directory (`~/.claude.json`, `~/.gemini/settings.json`) are auto-enabled since they're user-owned. Servers from project-level files (`.vscode/mcp.json`, `.mcp.json`, `.cursor/mcp.json`) are discovered but **disabled by default** for security — ask the assistant to add them or use `mcp_discover(action: "add")`.
135135

136136
!!! tip
137-
Home-directory MCP servers (from `~/.claude.json`, `~/.gemini/settings.json`) are loaded automatically. Project-scoped servers require explicit approval via `/discover-and-add-mcps` or `mcp_discover(action: "add")`.
137+
Home-directory MCP servers (from `~/.claude.json`, `~/.gemini/settings.json`) are loaded automatically. Project-scoped servers require explicit approval via `mcp_discover(action: "add")`.
138138

139139
!!! warning "Security: untrusted repositories"
140140
Project-level MCP configs (`.vscode/mcp.json`, `.mcp.json`, `.cursor/mcp.json`) are discovered but not auto-connected. This prevents malicious repositories from executing arbitrary commands. You must explicitly approve project-scoped servers before they run.

docs/docs/reference/security-faq.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ Altimate Code can automatically discover MCP server definitions from other AI to
168168
**Security model:**
169169

170170
- **Home-directory configs** (your personal machine config) are treated as trusted and auto-enabled, since you installed them.
171-
- **Project-scoped configs** (checked into a repo) are discovered but **disabled by default**. You must explicitly approve them via the `/discover-and-add-mcps` tool before they run.
171+
- **Project-scoped configs** (checked into a repo) are discovered but **disabled by default**. You must explicitly approve them via the `mcp_discover` tool before they run.
172172
- **Sensitive details are redacted** in discovery notifications. Server commands and URLs are only shown when you explicitly inspect them.
173173
- **Prototype pollution, command injection, and path traversal** are hardened against with input validation and `Object.create(null)` result objects.
174174

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 via /discover-and-add-mcps. Home-directory configs (~/.claude.json,
168+
* approve them by asking the assistant to add them. 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/mcp/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ export namespace MCP {
215215

216216
// altimate_change start — show discovery toast after MCP connections complete
217217
if (discoveryResult) {
218-
const message = `Discovered ${discoveryResult.serverNames.length} new MCP server(s): ${discoveryResult.serverNames.join(", ")}. Run /discover-and-add-mcps to enable and add them.`
218+
const message = `Discovered ${discoveryResult.serverNames.length} new MCP server(s): ${discoveryResult.serverNames.join(", ")}. Ask the assistant to add them, or they will be available automatically in the current session.`
219219
Bus.publish(TuiEvent.ToastShow, {
220220
title: "MCP Servers Discovered",
221221
message,

packages/opencode/src/provider/provider.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1071,11 +1071,27 @@ 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"])
1080+
// altimate_change end
10741081
for (const [id, provider] of Object.entries(database)) {
10751082
const providerID = ProviderID.make(id)
10761083
if (disabled.has(providerID)) continue
10771084
const apiKey = provider.env.map((item) => env[item]).find(Boolean)
10781085
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 })
1091+
continue
1092+
}
1093+
}
1094+
// altimate_change end
10791095
mergeProvider(providerID, {
10801096
source: "env",
10811097
key: provider.env.length === 1 ? apiKey : undefined,

packages/opencode/src/session/processor.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,9 @@ export namespace SessionProcessor {
394394
})
395395
} else {
396396
const retry = SessionRetry.retryable(error)
397-
if (retry !== undefined) {
397+
// altimate_change start — cap retries to avoid infinite retry loops (e.g. GitHub Models rate limits in Codespaces)
398+
if (retry !== undefined && attempt < SessionRetry.RETRY_MAX_ATTEMPTS) {
399+
// altimate_change end
398400
attempt++
399401
const delay = SessionRetry.delay(attempt, error.name === "APIError" ? error : undefined)
400402
SessionStatus.set(input.sessionID, {

packages/opencode/src/session/retry.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ export namespace SessionRetry {
77
export const RETRY_BACKOFF_FACTOR = 2
88
export const RETRY_MAX_DELAY_NO_HEADERS = 30_000 // 30 seconds
99
export const RETRY_MAX_DELAY = 2_147_483_647 // max 32-bit signed integer for setTimeout
10+
// altimate_change start — max retry attempts to prevent infinite retry loops
11+
export const RETRY_MAX_ATTEMPTS = 5
12+
// altimate_change end
1013

1114
export async function sleep(ms: number, signal: AbortSignal): Promise<void> {
1215
return new Promise((resolve, reject) => {

packages/opencode/test/provider/provider.test.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2282,3 +2282,52 @@ test("cloudflare-ai-gateway forwards config metadata options", async () => {
22822282
},
22832283
})
22842284
})
2285+
2286+
// altimate_change start — test Codespace GITHUB_TOKEN skip logic
2287+
test("github-models is excluded when CODESPACES=true and only GITHUB_TOKEN is set", async () => {
2288+
await using tmp = await tmpdir({
2289+
init: async (dir) => {
2290+
await Bun.write(
2291+
path.join(dir, "opencode.json"),
2292+
JSON.stringify({
2293+
$schema: "https://altimate.ai/config.json",
2294+
}),
2295+
)
2296+
},
2297+
})
2298+
await Instance.provide({
2299+
directory: tmp.path,
2300+
init: async () => {
2301+
Env.set("GITHUB_TOKEN", "ghu_fake_codespace_token")
2302+
Env.set("CODESPACES", "true")
2303+
},
2304+
fn: async () => {
2305+
const providers = await Provider.list()
2306+
expect(providers["github-models"]).toBeUndefined()
2307+
},
2308+
})
2309+
})
2310+
2311+
test("github-models is available when GITHUB_TOKEN set without CODESPACES", async () => {
2312+
await using tmp = await tmpdir({
2313+
init: async (dir) => {
2314+
await Bun.write(
2315+
path.join(dir, "opencode.json"),
2316+
JSON.stringify({
2317+
$schema: "https://altimate.ai/config.json",
2318+
}),
2319+
)
2320+
},
2321+
})
2322+
await Instance.provide({
2323+
directory: tmp.path,
2324+
init: async () => {
2325+
Env.set("GITHUB_TOKEN", "ghp_real_personal_token")
2326+
},
2327+
fn: async () => {
2328+
const providers = await Provider.list()
2329+
expect(providers["github-models"]).toBeDefined()
2330+
},
2331+
})
2332+
})
2333+
// altimate_change end

0 commit comments

Comments
 (0)