feat(plugin): add shell.env hook for manipulating environment in tools and shell (#12012)

This commit is contained in:
Tyler Gannon
2026-02-03 15:18:41 -06:00
committed by GitHub
parent 25bdd77b1d
commit a30696f9bf
5 changed files with 32 additions and 1 deletions

View File

@@ -8,6 +8,7 @@ import type { WSContext } from "hono/ws"
import { Instance } from "../project/instance" import { Instance } from "../project/instance"
import { lazy } from "@opencode-ai/util/lazy" import { lazy } from "@opencode-ai/util/lazy"
import { Shell } from "@/shell/shell" import { Shell } from "@/shell/shell"
import { Plugin } from "@/plugin"
export namespace Pty { export namespace Pty {
const log = Log.create({ service: "pty" }) const log = Log.create({ service: "pty" })
@@ -102,9 +103,11 @@ export namespace Pty {
} }
const cwd = input.cwd || Instance.directory const cwd = input.cwd || Instance.directory
const shellEnv = await Plugin.trigger("shell.env", { cwd }, { env: {} })
const env = { const env = {
...process.env, ...process.env,
...input.env, ...input.env,
...shellEnv.env,
TERM: "xterm-256color", TERM: "xterm-256color",
OPENCODE_TERMINAL: "1", OPENCODE_TERMINAL: "1",
} as Record<string, string> } as Record<string, string>

View File

@@ -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 matchingInvocation = invocations[shellName] ?? invocations[""]
const args = matchingInvocation?.args const args = matchingInvocation?.args
const cwd = Instance.directory
const shellEnv = await Plugin.trigger("shell.env", { cwd }, { env: {} })
const proc = spawn(shell, args, { const proc = spawn(shell, args, {
cwd: Instance.directory, cwd,
detached: process.platform !== "win32", detached: process.platform !== "win32",
stdio: ["ignore", "pipe", "pipe"], stdio: ["ignore", "pipe", "pipe"],
env: { env: {
...process.env, ...process.env,
...shellEnv.env,
TERM: "dumb", TERM: "dumb",
}, },
}) })

View File

@@ -16,6 +16,7 @@ import { Shell } from "@/shell/shell"
import { BashArity } from "@/permission/arity" import { BashArity } from "@/permission/arity"
import { Truncate } from "./truncation" import { Truncate } from "./truncation"
import { Plugin } from "@/plugin"
const MAX_METADATA_LENGTH = 30_000 const MAX_METADATA_LENGTH = 30_000
const DEFAULT_TIMEOUT = Flag.OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS || 2 * 60 * 1000 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, { const proc = spawn(params.command, {
shell, shell,
cwd, cwd,
env: { env: {
...process.env, ...process.env,
...shellEnv.env,
}, },
stdio: ["ignore", "pipe", "pipe"], stdio: ["ignore", "pipe", "pipe"],
detached: process.platform !== "win32", detached: process.platform !== "win32",

View File

@@ -185,6 +185,7 @@ export interface Hooks {
input: { tool: string; sessionID: string; callID: string }, input: { tool: string; sessionID: string; callID: string },
output: { args: any }, output: { args: any },
) => Promise<void> ) => Promise<void>
"shell.env"?: (input: { cwd: string }, output: { env: Record<string, string> }) => Promise<void>
"tool.execute.after"?: ( "tool.execute.after"?: (
input: { tool: string; sessionID: string; callID: string }, input: { tool: string; sessionID: string; callID: string },
output: { output: {

View File

@@ -192,6 +192,10 @@ Plugins can subscribe to events as seen below in the Examples section. Here is a
- `todo.updated` - `todo.updated`
#### Shell Events
- `shell.env`
#### Tool Events #### Tool Events
- `tool.execute.after` - `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 ### Custom tools
Plugins can also add custom tools to opencode: Plugins can also add custom tools to opencode: