fix(non-interactive-env): prepend env vars directly to git command string
OpenCode's bash tool ignores args.env and uses hardcoded process.env in spawn(). Work around this by prepending GIT_EDITOR, EDITOR, VISUAL, and PAGER env vars directly to the command string. Only applies to git commands to avoid bloating non-git commands. Added shellEscape() and buildEnvPrefix() helper functions to properly escape env var values and construct the prefix string. 🤖 GENERATED WITH ASSISTANCE OF [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
import { describe, test, expect } from "bun:test"
|
||||
import { createNonInteractiveEnvHook, NON_INTERACTIVE_ENV } from "./index"
|
||||
|
||||
describe("non-interactive-env hook", () => {
|
||||
const mockCtx = {} as Parameters<typeof createNonInteractiveEnvHook>[0]
|
||||
|
||||
describe("git command modification", () => {
|
||||
test("#given git command #when hook executes #then prepends env vars", async () => {
|
||||
const hook = createNonInteractiveEnvHook(mockCtx)
|
||||
const output: { args: Record<string, unknown>; message?: string } = {
|
||||
args: { command: "git commit -m 'test'" },
|
||||
}
|
||||
|
||||
await hook["tool.execute.before"](
|
||||
{ tool: "bash", sessionID: "test", callID: "1" },
|
||||
output
|
||||
)
|
||||
|
||||
const cmd = output.args.command as string
|
||||
expect(cmd).toContain("GIT_EDITOR=:")
|
||||
expect(cmd).toContain("EDITOR=:")
|
||||
expect(cmd).toContain("PAGER=cat")
|
||||
expect(cmd).toEndWith(" git commit -m 'test'")
|
||||
})
|
||||
|
||||
test("#given non-git bash command #when hook executes #then command unchanged", async () => {
|
||||
const hook = createNonInteractiveEnvHook(mockCtx)
|
||||
const output: { args: Record<string, unknown>; message?: string } = {
|
||||
args: { command: "ls -la" },
|
||||
}
|
||||
|
||||
await hook["tool.execute.before"](
|
||||
{ tool: "bash", sessionID: "test", callID: "1" },
|
||||
output
|
||||
)
|
||||
|
||||
expect(output.args.command).toBe("ls -la")
|
||||
})
|
||||
|
||||
test("#given non-bash tool #when hook executes #then command unchanged", async () => {
|
||||
const hook = createNonInteractiveEnvHook(mockCtx)
|
||||
const output: { args: Record<string, unknown>; message?: string } = {
|
||||
args: { command: "git status" },
|
||||
}
|
||||
|
||||
await hook["tool.execute.before"](
|
||||
{ tool: "Read", sessionID: "test", callID: "1" },
|
||||
output
|
||||
)
|
||||
|
||||
expect(output.args.command).toBe("git status")
|
||||
})
|
||||
|
||||
test("#given empty command #when hook executes #then no error", async () => {
|
||||
const hook = createNonInteractiveEnvHook(mockCtx)
|
||||
const output: { args: Record<string, unknown>; message?: string } = {
|
||||
args: {},
|
||||
}
|
||||
|
||||
await hook["tool.execute.before"](
|
||||
{ tool: "bash", sessionID: "test", callID: "1" },
|
||||
output
|
||||
)
|
||||
|
||||
expect(output.args.command).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe("shell escaping", () => {
|
||||
test("#given git command #when building prefix #then VISUAL properly escaped", async () => {
|
||||
const hook = createNonInteractiveEnvHook(mockCtx)
|
||||
const output: { args: Record<string, unknown>; message?: string } = {
|
||||
args: { command: "git status" },
|
||||
}
|
||||
|
||||
await hook["tool.execute.before"](
|
||||
{ tool: "bash", sessionID: "test", callID: "1" },
|
||||
output
|
||||
)
|
||||
|
||||
const cmd = output.args.command as string
|
||||
expect(cmd).toContain("VISUAL=''")
|
||||
})
|
||||
|
||||
test("#given git command #when building prefix #then all NON_INTERACTIVE_ENV vars included", async () => {
|
||||
const hook = createNonInteractiveEnvHook(mockCtx)
|
||||
const output: { args: Record<string, unknown>; message?: string } = {
|
||||
args: { command: "git log" },
|
||||
}
|
||||
|
||||
await hook["tool.execute.before"](
|
||||
{ tool: "bash", sessionID: "test", callID: "1" },
|
||||
output
|
||||
)
|
||||
|
||||
const cmd = output.args.command as string
|
||||
for (const key of Object.keys(NON_INTERACTIVE_ENV)) {
|
||||
expect(cmd).toContain(`${key}=`)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe("banned command detection", () => {
|
||||
test("#given vim command #when hook executes #then warning message set", async () => {
|
||||
const hook = createNonInteractiveEnvHook(mockCtx)
|
||||
const output: { args: Record<string, unknown>; message?: string } = {
|
||||
args: { command: "vim file.txt" },
|
||||
}
|
||||
|
||||
await hook["tool.execute.before"](
|
||||
{ tool: "bash", sessionID: "test", callID: "1" },
|
||||
output
|
||||
)
|
||||
|
||||
expect(output.message).toContain("vim")
|
||||
expect(output.message).toContain("interactive")
|
||||
})
|
||||
|
||||
test("#given safe command #when hook executes #then no warning", async () => {
|
||||
const hook = createNonInteractiveEnvHook(mockCtx)
|
||||
const output: { args: Record<string, unknown>; message?: string } = {
|
||||
args: { command: "ls -la" },
|
||||
}
|
||||
|
||||
await hook["tool.execute.before"](
|
||||
{ tool: "bash", sessionID: "test", callID: "1" },
|
||||
output
|
||||
)
|
||||
|
||||
expect(output.message).toBeUndefined()
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user