Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
2a99a1c
fix(nix): watch scripts and patches in nix-hashes workflow
jerome-benoit Feb 9, 2026
fb5ba71
Migrate all process.env to Env namespace for consistent caching (#12698)
jerome-benoit Feb 9, 2026
2b0695e
test(env): add comprehensive tests for production and test mode behavior
jerome-benoit Feb 9, 2026
badedc7
fix(env): complete dot notation migration to Env namespace
jerome-benoit Feb 9, 2026
35ba9b8
Merge remote-tracking branch 'upstream/dev' into fix-env-caching-12698
jerome-benoit Feb 14, 2026
95a2dba
fix: migrate AWS_CONTAINER env vars to Env.get()
jerome-benoit Feb 14, 2026
8a3b5cb
fix: revert module-level Env.get() to process.env for test compatibility
jerome-benoit Feb 14, 2026
acb22fe
fix(ide): use process.env for test compatibility with direct env mani…
jerome-benoit Feb 14, 2026
c0a4104
Merge branch 'dev' into fix-env-caching-12698
jerome-benoit Feb 16, 2026
31b935a
fix(env): remove Env namespace, use direct process.env access
Feb 16, 2026
c0766ce
chore: delete empty env directory
Feb 16, 2026
f93bbe6
Merge branch 'dev' into fix-env-caching-12698
jerome-benoit Feb 16, 2026
b803e29
chore: remove obsolete process.env comments
Feb 16, 2026
6ef5dff
chore: remove .sisyphus files, add to gitignore
Feb 16, 2026
114fb18
fix: restore test isolation comment in global/index.ts
Feb 16, 2026
ce658f3
Merge branch 'dev' into fix-env-caching-12698
jerome-benoit Feb 16, 2026
717b80a
Merge branch 'dev' into fix-env-caching-12698
jerome-benoit Feb 17, 2026
5f72535
Merge branch 'dev' into fix-env-caching-12698
jerome-benoit Feb 18, 2026
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ ts-dist
.turbo
**/.serena
.serena/
.sisyphus/
/result
refs
Session.vim
Expand Down
3 changes: 2 additions & 1 deletion packages/opencode/src/cli/cmd/acp.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Log } from "@/util/log"

import { bootstrap } from "../bootstrap"
import { cmd } from "./cmd"
import { AgentSideConnection, ndJsonStream } from "@agentclientprotocol/sdk"
Expand All @@ -20,7 +21,7 @@ export const AcpCommand = cmd({
})
},
handler: async (args) => {
process.env.OPENCODE_CLIENT = "acp"
process.env["OPENCODE_CLIENT"] = "acp"
await bootstrap(process.cwd(), async () => {
const opts = await resolveNetworkOptions(args)
const server = Server.listen(opts)
Expand Down
1 change: 1 addition & 0 deletions packages/opencode/src/cli/cmd/auth.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Auth } from "../../auth"

import { cmd } from "./cmd"
import * as prompts from "@clack/prompts"
import { UI } from "../ui"
Expand Down
1 change: 1 addition & 0 deletions packages/opencode/src/cli/cmd/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type {
PullRequestEvent,
} from "@octokit/webhooks-types"
import { UI } from "../ui"

import { cmd } from "./cmd"
import { ModelsDev } from "../../provider/models"
import { Instance } from "@/project/instance"
Expand Down
3 changes: 2 additions & 1 deletion packages/opencode/src/cli/cmd/tui/attach.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { cmd } from "../cmd"
import { UI } from "@/cli/ui"
import { tui } from "./app"

import { win32DisableProcessedInput, win32InstallCtrlCGuard } from "./win32"

export const AttachCommand = cmd({
Expand Down Expand Up @@ -58,7 +59,7 @@ export const AttachCommand = cmd({
}
})()
const headers = (() => {
const password = args.password ?? process.env.OPENCODE_SERVER_PASSWORD
const password = args.password ?? process.env["OPENCODE_SERVER_PASSWORD"]
if (!password) return undefined
const auth = `Basic ${Buffer.from(`opencode:${password}`).toString("base64")}`
return { Authorization: auth }
Expand Down
3 changes: 2 additions & 1 deletion packages/opencode/src/cli/cmd/tui/thread.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Log } from "@/util/log"
import { withNetworkOptions, resolveNetworkOptions } from "@/cli/network"
import type { Event } from "@opencode-ai/sdk/v2"
import type { EventSource } from "./context/sdk"

import { win32DisableProcessedInput, win32InstallCtrlCGuard } from "./win32"

declare global {
Expand Down Expand Up @@ -93,7 +94,7 @@ export const TuiThreadCommand = cmd({
}

// Resolve relative paths against PWD to preserve behavior when using --cwd flag
const baseCwd = process.env.PWD ?? process.cwd()
const baseCwd = process.env["PWD"] ?? process.cwd()
const cwd = args.project ? path.resolve(baseCwd, args.project) : process.cwd()
const localWorker = new URL("./worker.ts", import.meta.url)
const distWorker = new URL("./cli/cmd/tui/worker.js", import.meta.url)
Expand Down
5 changes: 3 additions & 2 deletions packages/opencode/src/cli/cmd/uninstall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { UI } from "../ui"
import * as prompts from "@clack/prompts"
import { Installation } from "../../installation"
import { Global } from "../../global"

import { $ } from "bun"
import fs from "fs/promises"
import path from "path"
Expand Down Expand Up @@ -235,9 +236,9 @@ async function executeUninstall(method: Installation.Method, targets: RemovalTar
}

async function getShellConfigFile(): Promise<string | null> {
const shell = path.basename(process.env.SHELL || "bash")
const shell = path.basename(process.env["SHELL"] || "bash")
const home = os.homedir()
const xdgConfig = process.env.XDG_CONFIG_HOME || path.join(home, ".config")
const xdgConfig = process.env["XDG_CONFIG_HOME"] || path.join(home, ".config")

const configFiles: Record<string, string[]> = {
fish: [path.join(xdgConfig, "fish", "config.fish")],
Expand Down
1 change: 1 addition & 0 deletions packages/opencode/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import path from "path"
import { pathToFileURL } from "url"
import os from "os"
import z from "zod"

import { Filesystem } from "../util/filesystem"
import { ModelsDev } from "../provider/models"
import { mergeDeep, pipe, unique } from "remeda"
Expand Down
28 changes: 0 additions & 28 deletions packages/opencode/src/env/index.ts

This file was deleted.

1 change: 0 additions & 1 deletion packages/opencode/src/ide/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { spawn } from "bun"
import z from "zod"
import { NamedError } from "@opencode-ai/util/error"
import { Log } from "../util/log"

const SUPPORTED_IDES = [
{ name: "Windsurf" as const, cmd: "windsurf" },
{ name: "Visual Studio Code - Insiders" as const, cmd: "code-insiders" },
Expand Down
5 changes: 3 additions & 2 deletions packages/opencode/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { McpCommand } from "./cli/cmd/mcp"
import { GithubCommand } from "./cli/cmd/github"
import { ExportCommand } from "./cli/cmd/export"
import { ImportCommand } from "./cli/cmd/import"

import { AttachCommand } from "./cli/cmd/tui/attach"
import { TuiThreadCommand } from "./cli/cmd/tui/thread"
import { AcpCommand } from "./cli/cmd/acp"
Expand Down Expand Up @@ -72,8 +73,8 @@ const cli = yargs(hideBin(process.argv))
})(),
})

process.env.AGENT = "1"
process.env.OPENCODE = "1"
process.env["AGENT"] = "1"
process.env["OPENCODE"] = "1"

Log.Default.info("opencode", {
version: Installation.VERSION,
Expand Down
72 changes: 37 additions & 35 deletions packages/opencode/src/provider/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { Plugin } from "../plugin"
import { ModelsDev } from "./models"
import { NamedError } from "@opencode-ai/util/error"
import { Auth } from "../auth"
import { Env } from "../env"

import { Instance } from "../project/instance"
import { Flag } from "../flag/flag"
import { iife } from "@/util/iife"
Expand Down Expand Up @@ -59,9 +59,12 @@ export namespace Provider {

function googleVertexVars(options: Record<string, any>) {
const project =
options["project"] ?? Env.get("GOOGLE_CLOUD_PROJECT") ?? Env.get("GCP_PROJECT") ?? Env.get("GCLOUD_PROJECT")
options["project"] ??
process.env["GOOGLE_CLOUD_PROJECT"] ??
process.env["GCP_PROJECT"] ??
process.env["GCLOUD_PROJECT"]
const location =
options["location"] ?? Env.get("GOOGLE_CLOUD_LOCATION") ?? Env.get("VERTEX_LOCATION") ?? "us-central1"
options["location"] ?? process.env["GOOGLE_CLOUD_LOCATION"] ?? process.env["VERTEX_LOCATION"] ?? "us-central1"
const endpoint = location === "global" ? "aiplatform.googleapis.com" : `${location}-aiplatform.googleapis.com`

return {
Expand All @@ -76,7 +79,7 @@ export namespace Provider {
if (typeof raw !== "string") return raw
const vars = model.providerID === "google-vertex" ? googleVertexVars(options) : undefined
return raw.replace(/\$\{([^}]+)\}/g, (match, key) => {
const val = Env.get(String(key)) ?? vars?.[String(key) as keyof typeof vars]
const val = process.env[String(key)] ?? vars?.[String(key) as keyof typeof vars]
return val ?? match
})
}
Expand Down Expand Up @@ -127,7 +130,7 @@ export namespace Provider {
},
async opencode(input) {
const hasKey = await (async () => {
const env = Env.all()
const env = process.env
if (input.env.some((item) => env[item])) return true
if (await Auth.get(input.id)) return true
const config = await Config.get()
Expand Down Expand Up @@ -190,7 +193,7 @@ export namespace Provider {
}
},
"azure-cognitive-services": async () => {
const resourceName = Env.get("AZURE_COGNITIVE_SERVICES_RESOURCE_NAME")
const resourceName = process.env["AZURE_COGNITIVE_SERVICES_RESOURCE_NAME"]
return {
autoload: false,
async getModel(sdk: any, modelID: string, options?: Record<string, any>) {
Expand All @@ -213,32 +216,30 @@ export namespace Provider {

// Region precedence: 1) config file, 2) env var, 3) default
const configRegion = providerConfig?.options?.region
const envRegion = Env.get("AWS_REGION")
const envRegion = process.env["AWS_REGION"]
const defaultRegion = configRegion ?? envRegion ?? "us-east-1"

// Profile: config file takes precedence over env var
const configProfile = providerConfig?.options?.profile
const envProfile = Env.get("AWS_PROFILE")
const envProfile = process.env["AWS_PROFILE"]
const profile = configProfile ?? envProfile

const awsAccessKeyId = Env.get("AWS_ACCESS_KEY_ID")
const awsAccessKeyId = process.env["AWS_ACCESS_KEY_ID"]

// TODO: Using process.env directly because Env.set only updates a process.env shallow copy,
// until the scope of the Env API is clarified (test only or runtime?)
const awsBearerToken = iife(() => {
const envToken = process.env.AWS_BEARER_TOKEN_BEDROCK
const envToken = process.env["AWS_BEARER_TOKEN_BEDROCK"]
if (envToken) return envToken
if (auth?.type === "api") {
process.env.AWS_BEARER_TOKEN_BEDROCK = auth.key
process.env["AWS_BEARER_TOKEN_BEDROCK"] = auth.key
return auth.key
}
return undefined
})

const awsWebIdentityTokenFile = Env.get("AWS_WEB_IDENTITY_TOKEN_FILE")
const awsWebIdentityTokenFile = process.env["AWS_WEB_IDENTITY_TOKEN_FILE"]

const containerCreds = Boolean(
process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI || process.env.AWS_CONTAINER_CREDENTIALS_FULL_URI,
process.env["AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"] || process.env["AWS_CONTAINER_CREDENTIALS_FULL_URI"],
)

if (!profile && !awsAccessKeyId && !awsBearerToken && !awsWebIdentityTokenFile && !containerCreds)
Expand Down Expand Up @@ -380,12 +381,15 @@ export namespace Provider {
"google-vertex": async (provider) => {
const project =
provider.options?.project ??
Env.get("GOOGLE_CLOUD_PROJECT") ??
Env.get("GCP_PROJECT") ??
Env.get("GCLOUD_PROJECT")
process.env["GOOGLE_CLOUD_PROJECT"] ??
process.env["GCP_PROJECT"] ??
process.env["GCLOUD_PROJECT"]

const location =
provider.options?.location ?? Env.get("GOOGLE_CLOUD_LOCATION") ?? Env.get("VERTEX_LOCATION") ?? "us-central1"
provider.options?.location ??
process.env["GOOGLE_CLOUD_LOCATION"] ??
process.env["VERTEX_LOCATION"] ??
"us-central1"

const autoload = Boolean(project)
if (!autoload) return { autoload: false }
Expand Down Expand Up @@ -414,8 +418,8 @@ export namespace Provider {
}
},
"google-vertex-anthropic": async () => {
const project = Env.get("GOOGLE_CLOUD_PROJECT") ?? Env.get("GCP_PROJECT") ?? Env.get("GCLOUD_PROJECT")
const location = Env.get("GOOGLE_CLOUD_LOCATION") ?? Env.get("VERTEX_LOCATION") ?? "global"
const project = process.env["GOOGLE_CLOUD_PROJECT"] ?? process.env["GCP_PROJECT"] ?? process.env["GCLOUD_PROJECT"]
const location = process.env["GOOGLE_CLOUD_LOCATION"] ?? process.env["VERTEX_LOCATION"] ?? "global"
const autoload = Boolean(project)
if (!autoload) return { autoload: false }
return {
Expand All @@ -432,19 +436,17 @@ export namespace Provider {
},
"sap-ai-core": async () => {
const auth = await Auth.get("sap-ai-core")
// TODO: Using process.env directly because Env.set only updates a shallow copy (not process.env),
// until the scope of the Env API is clarified (test only or runtime?)
const envServiceKey = iife(() => {
const envAICoreServiceKey = process.env.AICORE_SERVICE_KEY
const envAICoreServiceKey = process.env["AICORE_SERVICE_KEY"]
if (envAICoreServiceKey) return envAICoreServiceKey
if (auth?.type === "api") {
process.env.AICORE_SERVICE_KEY = auth.key
process.env["AICORE_SERVICE_KEY"] = auth.key
return auth.key
}
return undefined
})
const deploymentId = process.env.AICORE_DEPLOYMENT_ID
const resourceGroup = process.env.AICORE_RESOURCE_GROUP
const deploymentId = process.env["AICORE_DEPLOYMENT_ID"]
const resourceGroup = process.env["AICORE_RESOURCE_GROUP"]

return {
autoload: !!envServiceKey,
Expand All @@ -466,13 +468,13 @@ export namespace Provider {
}
},
gitlab: async (input) => {
const instanceUrl = Env.get("GITLAB_INSTANCE_URL") || "https://gitlab.com"
const instanceUrl = process.env["GITLAB_INSTANCE_URL"] || "https://gitlab.com"

const auth = await Auth.get(input.id)
const apiKey = await (async () => {
if (auth?.type === "oauth") return auth.access
if (auth?.type === "api") return auth.key
return Env.get("GITLAB_TOKEN")
return process.env["GITLAB_TOKEN"]
})()

const config = await Config.get()
Expand Down Expand Up @@ -508,11 +510,11 @@ export namespace Provider {
}
},
"cloudflare-workers-ai": async (input) => {
const accountId = Env.get("CLOUDFLARE_ACCOUNT_ID")
const accountId = process.env["CLOUDFLARE_ACCOUNT_ID"]
if (!accountId) return { autoload: false }

const apiKey = await iife(async () => {
const envToken = Env.get("CLOUDFLARE_API_KEY")
const envToken = process.env["CLOUDFLARE_API_KEY"]
if (envToken) return envToken
const auth = await Auth.get(input.id)
if (auth?.type === "api") return auth.key
Expand All @@ -531,14 +533,14 @@ export namespace Provider {
}
},
"cloudflare-ai-gateway": async (input) => {
const accountId = Env.get("CLOUDFLARE_ACCOUNT_ID")
const gateway = Env.get("CLOUDFLARE_GATEWAY_ID")
const accountId = process.env["CLOUDFLARE_ACCOUNT_ID"]
const gateway = process.env["CLOUDFLARE_GATEWAY_ID"]

if (!accountId || !gateway) return { autoload: false }

// Get API token from env or auth - required for authenticated gateways
const apiToken = await (async () => {
const envToken = Env.get("CLOUDFLARE_API_TOKEN") || Env.get("CF_AIG_TOKEN")
const envToken = process.env["CLOUDFLARE_API_TOKEN"] || process.env["CF_AIG_TOKEN"]
if (envToken) return envToken
const auth = await Auth.get(input.id)
if (auth?.type === "api") return auth.key
Expand Down Expand Up @@ -892,7 +894,7 @@ export namespace Provider {
}

// load env
const env = Env.all()
const env = process.env
for (const [providerID, provider] of Object.entries(database)) {
if (disabled.has(providerID)) continue
const apiKey = provider.env.map((item) => env[item]).find(Boolean)
Expand Down
7 changes: 4 additions & 3 deletions packages/opencode/src/shell/shell.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Flag } from "@/flag/flag"

import { lazy } from "@/util/lazy"
import path from "path"
import { spawn, type ChildProcess } from "child_process"
Expand Down Expand Up @@ -45,7 +46,7 @@ export namespace Shell {
const bash = path.join(git, "..", "..", "bin", "bash.exe")
if (Bun.file(bash).size) return bash
}
return process.env.COMSPEC || "cmd.exe"
return process.env["COMSPEC"] || "cmd.exe"
}
if (process.platform === "darwin") return "/bin/zsh"
const bash = Bun.which("bash")
Expand All @@ -54,13 +55,13 @@ export namespace Shell {
}

export const preferred = lazy(() => {
const s = process.env.SHELL
const s = process.env["SHELL"]
if (s) return s
return fallback()
})

export const acceptable = lazy(() => {
const s = process.env.SHELL
const s = process.env["SHELL"]
if (s && !BLACKLIST.has(process.platform === "win32" ? path.win32.basename(s) : path.basename(s))) return s
return fallback()
})
Expand Down
7 changes: 6 additions & 1 deletion packages/opencode/src/util/proxied.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
export function proxied() {
return !!(process.env.HTTP_PROXY || process.env.HTTPS_PROXY || process.env.http_proxy || process.env.https_proxy)
return !!(
process.env["HTTP_PROXY"] ||
process.env["HTTPS_PROXY"] ||
process.env["http_proxy"] ||
process.env["https_proxy"]
)
}
Loading
Loading