Repository navigation
Release v6.9.8 - #68
Conversation
Each queued message now shows a clickable [remove] action next to the existing [send now] action, letting users drop messages they no longer want to send without waiting for them to be drained. Bumps version to 6.9.8.
- Background commands: Bash gains background parameter for detached long-running commands - check_background and kill_background tools for managing background shells - Background shells panel in status bar (ctrl+b) - Remove queued messages action in queue panel
There was a problem hiding this comment.
🧪 PR Review is completed: Well-structured background-command feature; the critical catch is the Bash schema adding background to properties without adding it to required (strict-mode providers reject the whole tool schema), plus Windows cmd.exe quoting parity, an unhandled write-stream error, temp-file cleanup on exit, and unbounded log reads. Reviewed src/core/agent.ts: no issues found. Reviewed src/ui/App.tsx: no issues found. Reviewed src/ui/components/BackgroundShells.tsx: no issues found. Reviewed src/tools/executors/executeCommand.ts: no issues found. Reviewed src/tools/executors/checkBackground.ts: no issues found. Reviewed src/tools/executors/killBackground.ts: no issues found. Reviewed test/background-commands.test.ts: no issues found. Reviewed src/prompts/system.ts: no issues found. Reviewed src/ui/components/StatusBar.tsx: no issues found. Reviewed src/ui/components/QueuedMessages.tsx: no issues found. Reviewed src/tools/index.ts: no issues found. Reviewed src/tools/schemas/index.ts: no issues found. Reviewed src/tools/schemas/check_background.ts: no issues found. Reviewed src/tools/schemas/kill_background.ts: no issues found. Reviewed src/tools/types.ts: no issues found. Reviewed package.json: no issues found.
Skipped files
CHANGELOG.md: Skipped file pattern
⬇️ Low Priority Suggestions (4)
src/tools/executors/backgroundCommands.ts (4 suggestions)
Location:
src/tools/executors/backgroundCommands.ts(Lines 127-132)🟠 Platform Parity Bug
Issue: The foreground spawn in
executeCommand.tssetswindowsVerbatimArgumentsfor cmd.exe because "cmd.exe parses the command string itself; pre-quoting would corrupt it". The background spawn here omits that option, so on Windows without Git Bash (cmd.exe fallback), Node's default MSVCRT argument escaping mangles any command containing quotes (e.g.echo "hi"reaches cmd as\"hi\"), silently corrupting background commands.Fix: Apply the same verbatim-arguments rule to the background spawn (add
import { isCmdShell } from "../../utils/shell.js").Impact: Background commands behave identically to foreground ones under cmd.exe.
- const child = spawn(shell, shellArgs, { - cwd, - env: { ...process.env, TERM: "dumb" }, - stdio: ["ignore", "pipe", "pipe"], - detached: process.platform !== "win32", - }) + const child = spawn(shell, shellArgs, { + cwd, + env: { ...process.env, TERM: "dumb" }, + stdio: ["ignore", "pipe", "pipe"], + detached: process.platform !== "win32", + windowsVerbatimArguments: process.platform === "win32" && isCmdShell(shell), + })Location:
src/tools/executors/backgroundCommands.ts(Lines 137-139)🟠 Crash Risk
Issue:
fs.createWriteStream(outputFile)has no 'error' listener. If the log write fails — disk full during a long build (the exact use case for background commands), tmpdir removed, or EMFILE — the stream emits an unhandled 'error' event, which throws an uncaught exception and kills the whole CLI mid-turn. Every other filesystem helper in this file (deleteOutputFile,readOutput,readBackgroundOutputTail) is best-effort, but this one is not.Fix: Attach an error listener so a failed log write degrades gracefully; output reads already fall back to
cmd.output, andfinalize'sendcallback still runs so status updates correctly.Impact: A full disk or unwritable tmpdir no longer crashes the agent while a background command is running.
- const outStream = fs.createWriteStream(outputFile) - child.stdout.on("data", (data) => outStream.write(data)) - child.stderr.on("data", (data) => outStream.write(data)) + const outStream = fs.createWriteStream(outputFile) + outStream.on("error", () => {}) + child.stdout.on("data", (data) => outStream.write(data)) + child.stderr.on("data", (data) => outStream.write(data))Location:
src/tools/executors/backgroundCommands.ts(Lines 60-69)🟠 Resource Leak
Issue: The exit hook SIGTERMs running commands but never deletes their temp output files, and neither
pruneExpirednorresetBackgroundCommandsruns at shutdown. Every session that uses background commands leavesorbcode-bg-*.logfiles — containing full command output, potentially sensitive — in the tmpdir indefinitely, accumulating across sessions.Fix: Delete each command's output file in the exit hook;
fs.rmSyncinsidedeleteOutputFileis synchronous and safe within the 'exit' handler (children keep writing to the unlinked inode, which is harmless).Impact: No orphaned log files accumulating in the tmpdir; command output no longer outlives the CLI process.
- /** Detached children outlive us by default; take them down when we exit. */ - function installExitHook() { - if (exitHookInstalled) return - exitHookInstalled = true - process.once("exit", () => { - for (const cmd of commands.values()) { - if (cmd.status === "running") signalCommand(cmd, "SIGTERM") - } - }) - } + /** Detached children outlive us by default; take them down when we exit. */ + function installExitHook() { + if (exitHookInstalled) return + exitHookInstalled = true + process.once("exit", () => { + for (const cmd of commands.values()) { + if (cmd.status === "running") signalCommand(cmd, "SIGTERM") + deleteOutputFile(cmd) + } + }) + }Location:
src/tools/executors/backgroundCommands.ts(Lines 71-77)🟡 Performance
Issue:
readOutputreads the entire log file into memory andgetBackgroundCommandruns it on every call, so eachcheck_background/kill_backgroundsynchronously loads the full output of a potentially huge build log, blocking the event loop and spiking memory.checkBackgroundonly ever displays the first 30k chars, and the UI already uses the boundedreadBackgroundOutputTail, so nothing needs the unbounded read.Fix: Bound the read to the first
MAX_OUTPUT_READ_BYTES(defineconst MAX_OUTPUT_READ_BYTES = 1024 * 1024next toRETENTION_MS). This is behavior-preserving since all consumers truncate far below that bound.Impact: check_background stays cheap and non-blocking no matter how chatty the background command is.
- function readOutput(cmd: BackgroundCommand): string { - try { - return fs.readFileSync(cmd.outputFile, "utf-8") - } catch { - return cmd.output - } - } + function readOutput(cmd: BackgroundCommand): string { + try { + const fd = fs.openSync(cmd.outputFile, "r") + try { + const length = Math.min(fs.fstatSync(fd).size, MAX_OUTPUT_READ_BYTES) + const buffer = Buffer.alloc(length) + fs.readSync(fd, buffer, 0, length, 0) + return buffer.toString("utf-8") + } finally { + fs.closeSync(fd) + } + } catch { + return cmd.output + } + }
Changes
backgroundparameter for detached long-running commands (downloads, builds, tests, installs)Version
6.9.8