diff --git a/packages/opencode/src/session/system.ts b/packages/opencode/src/session/system.ts index 06c71fa7dbdd..e5dad5401c85 100644 --- a/packages/opencode/src/session/system.ts +++ b/packages/opencode/src/session/system.ts @@ -58,6 +58,12 @@ export const layer = Layer.effect( ` Platform: ${process.platform}`, ` Today's date: ${new Date().toDateString()}`, ``, + ``, + `## System Safety (Critical)`, + ``, + `- **NEVER** run commands that kill all Node.js processes (e.g., \`taskkill /F /IM node.exe\`, \`killall node\`, \`pkill node\`, \`Get-Process node | Stop-Process\`, etc.).`, + `- OpenCode is built on Node.js (\`node.exe\`). Killing all Node processes will immediately crash the AI assistant and terminate the current session.`, + `- If you need to stop a specific process, target it by PID or use process-manager commands scoped to the project (e.g., \`npm stop\`, \`pm2 stop \`).`, ].join("\n"), ] }), diff --git a/packages/opencode/src/tool/shell.ts b/packages/opencode/src/tool/shell.ts index d3ca542684de..46c56ed858a4 100644 --- a/packages/opencode/src/tool/shell.ts +++ b/packages/opencode/src/tool/shell.ts @@ -28,6 +28,18 @@ export { Parameters } from "./shell/prompt" const MAX_METADATA_LENGTH = 30_000 const DEFAULT_TIMEOUT = Flag.OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS || 2 * 60 * 1000 const CWD = new Set(["cd", "chdir", "popd", "pushd", "push-location", "set-location"]) + +const DANGEROUS_COMMAND_PATTERNS = [ + /taskkill\s+.*\/?[Ff]\s+.*\/?[Ii][Mm]\s+node\.?exe/i, + /taskkill\s+.*\/?[Ii][Mm]\s+node\.?exe/i, + /killall\s+node/i, + /pkill\s+node/i, + /Get-Process\s+.*node\s*\|\s*Stop-Process/i, +] + +function isDangerousCommand(command: string): boolean { + return DANGEROUS_COMMAND_PATTERNS.some((pattern) => pattern.test(command)) +} const FILES = new Set([ ...CWD, "rm", @@ -601,6 +613,11 @@ export const ShellTool = Tool.define( throw new Error(`Invalid timeout value: ${params.timeout}. Timeout must be a positive number.`) } const timeout = params.timeout ?? DEFAULT_TIMEOUT + if (isDangerousCommand(params.command)) { + throw new Error( + `Command blocked for system safety: "${params.command}". This command would kill all Node.js processes, which crashes OpenCode because it is built on Node.js (node.exe). If you need to stop a specific process, target it by PID or use project-scoped commands like "npm stop" or "pm2 stop ".`, + ) + } const ps = Shell.ps(shell) yield* Effect.scoped( Effect.gen(function* () { diff --git a/packages/opencode/src/tool/shell/shell.txt b/packages/opencode/src/tool/shell/shell.txt index 5cba07805c1e..ca9837efdf43 100644 --- a/packages/opencode/src/tool/shell/shell.txt +++ b/packages/opencode/src/tool/shell/shell.txt @@ -8,6 +8,12 @@ Use `${tmp}` for temporary work outside the workspace. This directory has alread IMPORTANT: This tool is for terminal operations like git, npm, docker, etc. DO NOT use it for file operations (reading, writing, editing, searching, finding files) - use the specialized tools for this instead. +## System Safety (Critical) + +- **NEVER** run commands that kill all Node.js processes (e.g., `taskkill /F /IM node.exe`, `killall node`, `pkill node`, `Get-Process node | Stop-Process`, etc.). +- OpenCode is built on Node.js (`node.exe`). Killing all Node processes will immediately crash the AI assistant and terminate the current session. +- If you need to stop a specific process, target it by PID or use process-manager commands scoped to the project (e.g., `npm stop`, `pm2 stop `). + ${commandSection} # Committing changes with git