shell tweaks, better handling for windows (#5455)

Co-authored-by: GitHub Action <action@github.com>
This commit is contained in:
Aiden Cline
2025-12-12 14:11:07 -08:00
committed by GitHub
parent 91ab966921
commit 15caecdb45
6 changed files with 116 additions and 80 deletions

View File

@@ -14,11 +14,10 @@ import { Permission } from "@/permission"
import { fileURLToPath } from "url"
import { Flag } from "@/flag/flag.ts"
import path from "path"
import { iife } from "@/util/iife"
import { Shell } from "@/shell/shell"
const MAX_OUTPUT_LENGTH = Flag.OPENCODE_EXPERIMENTAL_BASH_MAX_OUTPUT_LENGTH || 30_000
const DEFAULT_TIMEOUT = Flag.OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS || 2 * 60 * 1000
const SIGKILL_TIMEOUT_MS = 200
export const log = Log.create({ service: "bash-tool" })
@@ -53,32 +52,7 @@ const parser = lazy(async () => {
// TODO: we may wanna rename this tool so it works better on other shells
export const BashTool = Tool.define("bash", async () => {
const shell = iife(() => {
const s = process.env.SHELL
if (s) {
const basename = path.basename(s)
if (!new Set(["fish", "nu"]).has(basename)) {
return s
}
}
if (process.platform === "darwin") {
return "/bin/zsh"
}
if (process.platform === "win32") {
// Let Bun / Node pick COMSPEC (usually cmd.exe)
// or explicitly:
return process.env.COMSPEC || true
}
const bash = Bun.which("bash")
if (bash) {
return bash
}
return true
})
const shell = Shell.acceptable()
log.info("bash tool using shell", { shell })
return {
@@ -261,51 +235,23 @@ export const BashTool = Tool.define("bash", async () => {
let aborted = false
let exited = false
const killTree = async () => {
const pid = proc.pid
if (!pid || exited) {
return
}
if (process.platform === "win32") {
await new Promise<void>((resolve) => {
const killer = spawn("taskkill", ["/pid", String(pid), "/f", "/t"], { stdio: "ignore" })
killer.once("exit", resolve)
killer.once("error", resolve)
})
return
}
try {
process.kill(-pid, "SIGTERM")
await Bun.sleep(SIGKILL_TIMEOUT_MS)
if (!exited) {
process.kill(-pid, "SIGKILL")
}
} catch (_e) {
proc.kill("SIGTERM")
await Bun.sleep(SIGKILL_TIMEOUT_MS)
if (!exited) {
proc.kill("SIGKILL")
}
}
}
const kill = () => Shell.killTree(proc, { exited: () => exited })
if (ctx.abort.aborted) {
aborted = true
await killTree()
await kill()
}
const abortHandler = () => {
aborted = true
void killTree()
void kill()
}
ctx.abort.addEventListener("abort", abortHandler, { once: true })
const timeoutTimer = setTimeout(() => {
timedOut = true
void killTree()
void kill()
}, timeout + 100)
await new Promise<void>((resolve, reject) => {