Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "Altimate Code",
"image": "mcr.microsoft.com/devcontainers/javascript-node:22",
"features": {
"ghcr.io/devcontainers/features/git:1": {},
"ghcr.io/shyim/devcontainers-features/bun:0": {
"version": "1.3.10"
}
},
"postCreateCommand": ".devcontainer/post-create.sh",
"customizations": {
"vscode": {
"extensions": [
"biomejs.biome",
"dbaeumer.vscode-eslint"
],
"settings": {
"terminal.integrated.defaultProfile.linux": "bash"
}
},
"codespaces": {
"openFiles": [
"README.md"
]
}
},
"forwardPorts": [],
"remoteEnv": {
"BUN_INSTALL": "/home/node/.bun",
"PATH": "/home/node/.bun/bin:${containerEnv:PATH}"
}
}
24 changes: 24 additions & 0 deletions .devcontainer/post-create.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/bash
set -e

echo "=== Altimate Code: Codespace Setup ==="

# Configure git (required for tests)
git config --global user.name "${GITHUB_USER:-codespace}"
git config --global user.email "${GITHUB_USER:-codespace}@users.noreply.github.com"

# Install dependencies
echo "Installing dependencies with Bun..."
bun install

echo ""
echo "=== Setup complete! ==="
echo ""
echo "Quick start:"
echo " bun run build # Build the CLI"
echo " bun test # Run tests"
echo " bun turbo typecheck # Type-check all packages"
echo ""
echo "To install altimate globally after building:"
echo " bun link"
echo ""
26 changes: 14 additions & 12 deletions .github/meta/commit.txt
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
fix: viewer UX improvements from 100-trace analysis
fix: Codespaces — skip machine-scoped `GITHUB_TOKEN`, cap retries, fix phantom command

- Collapse Files Changed after 5 entries with "Show all N files" toggle
- Rename "GENS" → "LLM Calls" in header cards
- Hide Tokens card when cost is $0 (not actionable without cost context)
- Hide Cost metric card when $0.00 (wasted space)
- Add prominent error summary banner right after header metrics
- Improved dbt outcome detection: catch [PASS], [ERROR], N of M, Compilation Error
- Outcome detection rate improved from 18% → 33% across 100 real traces
- Updated doc screenshots with cleaner samples
Closes #413

Tested across 100 real production traces: 0 crashes, 0 JS errors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Skip auto-enabling `github-models` and `github-copilot` providers in
machine environments (Codespaces: `CODESPACES=true`, GitHub Actions:
`GITHUB_ACTIONS=true`) when only machine-scoped tokens (`GITHUB_TOKEN`,
`GH_TOKEN`) are available. The Codespace/Actions token lacks
`models:read` scope needed for GitHub Models API.
- Cap retry attempts at 5 (`RETRY_MAX_ATTEMPTS`) to prevent infinite
retry loops. Log actionable warning when retries exhaust.
- Replace phantom `/discover-and-add-mcps` toast with actionable message.
- Add `.devcontainer/` config (Node 22, Bun 1.3.10) for Codespaces.
- Add 32 adversarial e2e tests covering full Codespace/Actions env
simulation, `GH_TOKEN`, token variations, config overrides, retry bounds.
- Update docs to reference `mcp_discover` tool.
3 changes: 3 additions & 0 deletions docs/docs/configure/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,9 @@ Access 150+ models through a single API key.

Uses your GitHub Copilot subscription. Authenticate with `altimate auth`.

!!! note "Codespaces & GitHub Actions"
In GitHub Codespaces and GitHub Actions, the machine-scoped `GITHUB_TOKEN` lacks `models:read` permission and cannot be used for GitHub Copilot or GitHub Models inference. altimate automatically skips these providers in machine environments. To use them, authenticate explicitly with `altimate auth` or set a personal access token with `models:read` scope as a Codespace secret.

## Snowflake Cortex

```json
Expand Down
4 changes: 2 additions & 2 deletions docs/docs/configure/tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,10 @@ The `mcp_discover` tool finds MCP servers configured in other AI coding tools an
- `mcp_discover(action: "add", scope: "project")` — Write new servers to `.altimate-code/altimate-code.json`
- `mcp_discover(action: "add", scope: "global")` — Write to the global config dir (`~/.config/opencode/`)

**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.
**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")`.

!!! tip
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")`.
Home-directory MCP servers (from `~/.claude.json`, `~/.gemini/settings.json`) are loaded automatically. Project-scoped servers require explicit approval via `mcp_discover(action: "add")`.

!!! warning "Security: untrusted repositories"
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.
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/reference/security-faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ Altimate Code can automatically discover MCP server definitions from other AI to
**Security model:**

- **Home-directory configs** (your personal machine config) are treated as trusted and auto-enabled, since you installed them.
- **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.
- **Project-scoped configs** (checked into a repo) are discovered but **not auto-connected**. They are loaded with `enabled: false` and shown in a notification. Ask the assistant to enable them, or disable auto-discovery entirely with `experimental.auto_mcp_discovery: false`.
- **Sensitive details are redacted** in discovery notifications. Server commands and URLs are only shown when you explicitly inspect them.
- **Prototype pollution, command injection, and path traversal** are hardened against with input validation and `Object.create(null)` result objects.

Expand Down
3 changes: 3 additions & 0 deletions docs/docs/usage/github.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ jobs:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
```

!!! important "LLM provider required"
The workflow `GITHUB_TOKEN` is for repository access only — it cannot be used for LLM inference. You must provide a separate API key (e.g., `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`) as a repository secret. GitHub Copilot and GitHub Models providers are automatically disabled in Actions environments.

### Triggers

| Event | Behavior |
Expand Down
2 changes: 1 addition & 1 deletion packages/opencode/src/mcp/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ export namespace MCP {

// altimate_change start — show discovery toast after MCP connections complete
if (discoveryResult) {
const message = `Discovered ${discoveryResult.serverNames.length} new MCP server(s): ${discoveryResult.serverNames.join(", ")}. Run /discover-and-add-mcps to enable and add them.`
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.`
Bus.publish(TuiEvent.ToastShow, {
title: "MCP Servers Discovered",
message,
Expand Down
30 changes: 30 additions & 0 deletions packages/opencode/src/provider/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1071,11 +1071,41 @@ export namespace Provider {

// load env
const env = Env.all()
// altimate_change start — skip github-models/github-copilot auto-enable from machine-scoped GITHUB_TOKEN
// In GitHub Codespaces and GitHub Actions, GITHUB_TOKEN is a machine-scoped
// token for repo operations, not for model inference. Auto-enabling these
// providers leads to immediate rate limiting ("Too Many Requests") and long
// retry loops. We detect these environments and skip auto-enable unless the
// user has explicitly set a different token.
//
// Environment detection (official GitHub docs):
// Codespaces: CODESPACES=true, CODESPACE_NAME set
// Actions: GITHUB_ACTIONS=true, CI=true
//
// Machine-scoped tokens to ignore: GITHUB_TOKEN and GH_TOKEN (gh CLI alias)
const isMachineEnv = env["CODESPACES"] === "true" || env["GITHUB_ACTIONS"] === "true"
const machineTokenNames = new Set(["GITHUB_TOKEN", "GH_TOKEN"])
const skipGithubProviders = new Set(["github-models", "github-copilot", "github-copilot-enterprise"])
// altimate_change end
for (const [id, provider] of Object.entries(database)) {
const providerID = ProviderID.make(id)
if (disabled.has(providerID)) continue
const apiKey = provider.env.map((item) => env[item]).find(Boolean)
if (!apiKey) continue
// altimate_change start — skip GitHub providers when only machine-scoped tokens exist
if (isMachineEnv && skipGithubProviders.has(id)) {
// Check if ALL env vars for this provider are machine-scoped tokens
const matchedEnvVars = provider.env.filter((item) => env[item])
const allMachineScoped = matchedEnvVars.every((item) => machineTokenNames.has(item))
if (allMachineScoped) {
log.info("skipping provider in machine environment (token is not for model inference)", {
providerID,
environment: env["CODESPACES"] ? "codespace" : "github-actions",
})
continue
}
}
// altimate_change end
mergeProvider(providerID, {
source: "env",
key: provider.env.length === 1 ? apiKey : undefined,
Expand Down
14 changes: 13 additions & 1 deletion packages/opencode/src/session/processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,9 @@ export namespace SessionProcessor {
})
} else {
const retry = SessionRetry.retryable(error)
if (retry !== undefined) {
// altimate_change start — cap retries to avoid infinite loops, log on exhaustion
if (retry !== undefined && attempt < SessionRetry.RETRY_MAX_ATTEMPTS) {
// altimate_change end
attempt++
const delay = SessionRetry.delay(attempt, error.name === "APIError" ? error : undefined)
SessionStatus.set(input.sessionID, {
Expand All @@ -406,6 +408,16 @@ export namespace SessionProcessor {
await SessionRetry.sleep(delay, input.abort).catch(() => {})
continue
}
// altimate_change start — log when retries exhausted for debugging
if (retry !== undefined) {
log.warn("max retry attempts reached, giving up", {
attempt,
message: retry,
providerID: input.model.providerID,
modelID: input.model.id,
})
}
// altimate_change end
input.assistantMessage.error = error
Bus.publish(Session.Event.Error, {
sessionID: input.assistantMessage.sessionID,
Expand Down
3 changes: 3 additions & 0 deletions packages/opencode/src/session/retry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ export namespace SessionRetry {
export const RETRY_BACKOFF_FACTOR = 2
export const RETRY_MAX_DELAY_NO_HEADERS = 30_000 // 30 seconds
export const RETRY_MAX_DELAY = 2_147_483_647 // max 32-bit signed integer for setTimeout
// altimate_change start — max retry attempts to prevent infinite retry loops
export const RETRY_MAX_ATTEMPTS = 5
// altimate_change end

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