From a30696f9bfefc58d640316c6a864c9bb255de690 Mon Sep 17 00:00:00 2001 From: Tyler Gannon Date: Tue, 3 Feb 2026 15:18:41 -0600 Subject: [PATCH] feat(plugin): add shell.env hook for manipulating environment in tools and shell (#12012) --- packages/opencode/src/pty/index.ts | 3 +++ packages/opencode/src/session/prompt.ts | 5 ++++- packages/opencode/src/tool/bash.ts | 3 +++ packages/plugin/src/index.ts | 1 + packages/web/src/content/docs/plugins.mdx | 21 +++++++++++++++++++++ 5 files changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/pty/index.ts b/packages/opencode/src/pty/index.ts index d01b2b02e..a27ee9a74 100644 --- a/packages/opencode/src/pty/index.ts +++ b/packages/opencode/src/pty/index.ts @@ -8,6 +8,7 @@ import type { WSContext } from "hono/ws" import { Instance } from "../project/instance" import { lazy } from "@opencode-ai/util/lazy" import { Shell } from "@/shell/shell" +import { Plugin } from "@/plugin" export namespace Pty { const log = Log.create({ service: "pty" }) @@ -102,9 +103,11 @@ export namespace Pty { } const cwd = input.cwd || Instance.directory + const shellEnv = await Plugin.trigger("shell.env", { cwd }, { env: {} }) const env = { ...process.env, ...input.env, + ...shellEnv.env, TERM: "xterm-256color", OPENCODE_TERMINAL: "1", } as Record diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index b85a7c336..ab1eba5be 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -1500,12 +1500,15 @@ NOTE: At any point in time through this workflow you should feel free to ask the const matchingInvocation = invocations[shellName] ?? invocations[""] const args = matchingInvocation?.args + const cwd = Instance.directory + const shellEnv = await Plugin.trigger("shell.env", { cwd }, { env: {} }) const proc = spawn(shell, args, { - cwd: Instance.directory, + cwd, detached: process.platform !== "win32", stdio: ["ignore", "pipe", "pipe"], env: { ...process.env, + ...shellEnv.env, TERM: "dumb", }, }) diff --git a/packages/opencode/src/tool/bash.ts b/packages/opencode/src/tool/bash.ts index ff208ff3f..67559b78c 100644 --- a/packages/opencode/src/tool/bash.ts +++ b/packages/opencode/src/tool/bash.ts @@ -16,6 +16,7 @@ import { Shell } from "@/shell/shell" import { BashArity } from "@/permission/arity" import { Truncate } from "./truncation" +import { Plugin } from "@/plugin" const MAX_METADATA_LENGTH = 30_000 const DEFAULT_TIMEOUT = Flag.OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS || 2 * 60 * 1000 @@ -162,11 +163,13 @@ export const BashTool = Tool.define("bash", async () => { }) } + const shellEnv = await Plugin.trigger("shell.env", { cwd }, { env: {} }) const proc = spawn(params.command, { shell, cwd, env: { ...process.env, + ...shellEnv.env, }, stdio: ["ignore", "pipe", "pipe"], detached: process.platform !== "win32", diff --git a/packages/plugin/src/index.ts b/packages/plugin/src/index.ts index 86e7ae934..4cc84a5f3 100644 --- a/packages/plugin/src/index.ts +++ b/packages/plugin/src/index.ts @@ -185,6 +185,7 @@ export interface Hooks { input: { tool: string; sessionID: string; callID: string }, output: { args: any }, ) => Promise + "shell.env"?: (input: { cwd: string }, output: { env: Record }) => Promise "tool.execute.after"?: ( input: { tool: string; sessionID: string; callID: string }, output: { diff --git a/packages/web/src/content/docs/plugins.mdx b/packages/web/src/content/docs/plugins.mdx index ba530a6d9..394fecc40 100644 --- a/packages/web/src/content/docs/plugins.mdx +++ b/packages/web/src/content/docs/plugins.mdx @@ -192,6 +192,10 @@ Plugins can subscribe to events as seen below in the Examples section. Here is a - `todo.updated` +#### Shell Events + +- `shell.env` + #### Tool Events - `tool.execute.after` @@ -254,6 +258,23 @@ export const EnvProtection = async ({ project, client, $, directory, worktree }) --- +### Inject environment variables + +Inject environment variables into all shell execution (AI tools and user terminals): + +```javascript title=".opencode/plugins/inject-env.js" +export const InjectEnvPlugin = async () => { + return { + "shell.env": async (input, output) => { + output.env.MY_API_KEY = "secret" + output.env.PROJECT_ROOT = input.cwd + }, + } +} +``` + +--- + ### Custom tools Plugins can also add custom tools to opencode: