Extract applyDirenvPreflight() so the ACP client terminal and PTY backends
get the same direnv/devenv overlay as executeBash (previously only the
one-shot path did). The helper is a pure (command, env) transform — merge
direnv's set under the caller's overlay, prepend a regex-gated unset -v for
removed vars — so interactive backends keep their own env shape (live TERM)
while executeBash still layers its non-interactive defaults on top. The three
dispatch branches are mutually exclusive, so no command is preflighted twice.
Also drop the content-hash export cache in loadDirenvEnv: always run
direnv export json and let direnv's own watch/mtime invalidation decide
freshness, so a changed watched file re-exports even when .envrc is unchanged.
Co-Authored-By: seal <noreply@sealedsecurity.com>
Treat timeout 0 as an explicit no-deadline contract across the bash tool, executor, async job, and PTY paths.
Signed-off-by: Christian Stewart <christian@aperture.us>
- Stopped calling the consuming `getSteeringMessages` getter during mid-batch interrupt polls to prevent stranding or dropping messages before they reach the injection boundary.
- Skip subsequent steering checks in the poll loop once an interrupt has already triggered.
- Added a regression test to ensure legacy steering remains queued until the injection boundary when no non-consuming peek exists.
Propagated the native shell working directory in ShellRunResult so AgentSession can refresh cwd without running a hidden pwd command in the persistent shell.
Added regression coverage for cd plus a failing command followed by echo $?, proving cwd sync no longer overwrites the user's last shell status.
Fixes#3958
Updated interactive bash execution to query the persistent shell PWD after commands and move the session cwd when it changes, keeping the status line and session-scoped settings aligned with shell navigation.
Added regression coverage for syncing persistent shell directory changes back to the owning session.
Fixes#3958
`nohup cmd &` is now a transparent background wrapper that double-forks the
operand so it reparents to init (commit 00dcd54597). The shell only tracks
the short-lived intermediate fork, so `$!` is no longer the surviving
process — the prior test read `$!`, then `process.kill(pid, 0)` checked an
already-reaped pid and failed on Linux (the failing CI job).
Split into two contracts:
- plain `&` retention: stays a child of the shell, counted by
`liveBackgroundJobCount`, kept alive by the retain map; `$!` is the real
child pid we assert on.
- nohup reparenting: the operand writes its own pid before `exec`ing the
long sleep, and that (post-exec-stable) pid is asserted to survive across
turns — independent of `$!`.
- Added `Shell.liveBackgroundJobCount` to query active background processes.
- Retained per-call `:async:` shells if background jobs are still running upon turn completion.
- Reaped shells automatically once their last background process exits to prevent lingering processes.
brush-core's alias expander resolves aliases via
`value.split_ascii_whitespace()` (`crates/brush-core-vendored/src/interp.rs:1500`,
upstream brush issue reubeno/brush#57): each whitespace piece is dropped
into argv as-is, completely bypassing the shell parser. Any alias body
containing `(`, `)`, `|`, `&`, `;`, `<`, `>`, or `\`` therefore
turns the first piece into the command name, so Fedora's default
`alias which='(alias; declare -f) | /usr/bin/which …'` produces
`error: command not found: (alias;` for every `which` invocation.
The user's shell snapshot is generated by sourcing their real rc-file
under `/bin/bash` or `/bin/zsh` (so we can capture functions, options,
PATH) and then sourced by brush per-session. `sanitizeSnapshotForBrush`
now scans the emitted `alias -- NAME='VALUE'` lines after generation,
drops any whose decoded body contains those metacharacters, and rewrites
the file in place before caching. Compatible aliases (`ll='ls -l'`,
`gc='git --color=auto commit'`, embedded-quote `say='echo '\\''hi'\\'''`)
are preserved untouched; dropped names are logged at debug. brush then
falls through to whatever lives on `PATH`, which is what the user
expected when they ran `which` in the first place.
Covered by unit tests for the sanitizer (Fedora-which case, every
incompatible-metachar shape, every preserve case) and an integration
test that loads a poisoned snapshot and verifies `which sh` now exits
`0` with a real path.
Fixes#3234
Batch migration of 13 fs.rmSync calls to removeSyncWithRetries across:
- core/apply-patch.test.ts (4 calls)
- bash-executor.test.ts (4 calls)
- tools.test.ts (2 calls)
- compaction-hooks.test.ts (1 call)
- compaction-thinking-model.test.ts (2 calls)
Also exports removeSyncWithRetries from @oh-my-pi/pi-utils as a
standalone function for tests that manage their own temp dirs.
All tests pass: 139 pass, 0 fail across the 5 migrated files.
- Replaced Bun.sleep and wall-clock timing with fake timers (vi.useFakeTimers), release gates, and deterministic polling across 15+ test files to eliminate flakiness and improve speed.
- Consolidated per-test fixture setup into beforeAll/afterAll lifecycle hooks across 20+ test files, reducing redundant initialization and improving test performance by reusing shared immutable fixtures.
- Stubbed network calls in ModelRegistry and test discovery to prevent unintended outbound requests during test execution.
- Replaced subprocess-based test coordination (file markers, Bun.sleep polling) with in-memory fakes (FakeWebSocket, FakeLspServer, VirtualClock) for deterministic, fast test execution.
- Extended `AgentTool.concurrency` to accept per-call resolver functions and resolved concurrency mode from each tool call, falling back to exclusive on resolver errors.
- Updated BashTool to schedule non-PTY calls as shared and PTY calls as exclusive so non-interactive bash calls can run in parallel within one message.
- Tracked in-use persistent shell sessions in the bash executor and routed overlapping calls on the same session key to isolated one-shot shells while preserving owner session availability.
- Shared immutable model registries and auth storage via beforeAll/afterAll.
- Swapped fixed-delay settle sleeps for predicate polling and signals.
- Stubbed network/timers to drop wall-clock waits in registry and history tests.
- Added resetDisplay invalidation tests and startup-timing breakdown lines.
- Added Job::abort_internal_tasks to abort internal async tasks and drop their join handles.
- Updated shell cancellation paths to call this abort logic and handle mutable shell job lists before signaling remaining process groups.
- Added Rust and TypeScript tests that verify cancellation prevents background shell jobs from completing after abort.
- Replaced `Bun.sleep(50)` with `Promise.withResolvers` resolved on first chunk to avoid races.
- Used a filesystem marker file to detect shell startup before aborting in persistent-session test.
- Prevents flaky failures where aborts arrived before shell setup completed.
Quarantined persistent session keys only while the native cancellation promise remains unsettled, so healthy cleanup restores persistent mode and stalled cleanup cannot accumulate live shell instances.
Added coverage for both stalled and settled native cleanup paths.
Fixes#1347
Stopped marking persistent bash sessions as permanently broken when the JavaScript abort or timeout race wins.
Stopped the Rust descendant kill-wave helper once no cancellation targets remain so later commands are not swept into old cancels.
Fixes#1347
Raced bash execution against the JavaScript abort signal and timeout so the tool returns even when native shell cleanup does not settle.
Added regression coverage for native cleanup stalls on ESC abort and timeout.
Fixes#1347
- Added `providerRetryWait` and `retryWait` hooks to stream/usage options so tests bypass real scheduler delays.
- Parameterized GitHub Copilot poll intervals and Copilot model retry base delay for fast test execution.
- Replaced `Bun.sleep`/`setTimeout` polling loops with `AbortSignal` event listeners in agent session tests.
- Consolidated auth-gateway E2E helpers into a shared `test/helpers` module, eliminating duplicated `checkGatewayAvailable` implementations.
- Migrated credential-disabled tests from SQLite-backed stores to an in-memory store, removing temp-dir lifecycle overhead.
- Removed export leakage by demoting many helper and const symbols to module-local scope.
- Renamed underscore-prefixed internals and cache fields, then updated related references and `satisfies never` checks.
- Deleted obsolete logic branches and helpers, including harmony-stream interruption flow and unused benchmark runtime helpers.
- Updated Biome config and manifests by broadening lint coverage and removing an unused `@napi-rs/cli` dev dependency.
- Adjusted tests and utilities to use renamed test helpers and remove redundant private test-only helpers/locals.
The OutputSink now keeps a head budget (tools.artifactHeadBytes, default
20 KB) in addition to the tail spill window, so outputBytes can legally
reach head + tail + marker overhead. The multi-million line test still
asserted the pre-elision tail-only bound and started failing on CI.
- Added `toolStrictMode` support with `all_strict`/`none`/`mixed` options to OpenAI compatibility.
- Fixed OpenAI-completion strict-mode flows by capturing failed HTTP responses and retrying once as non-strict.
- Fixed completion error reporting by surfacing captured status, headers, and JSON `type`/`param`/`code` details.
- Improved strict-schema enforcement with WeakMap memoization and circular-schema detection in sanitization.
- Fixed OpenRouter provider lookup by resolving fallback model IDs for suffix and date variants in registry resolution.
- Refactored benchmark tooling and added async RPC error-window tracking for scheduled run execution.
- Replaced deprecated `await` tool wiring with `poll` in tool exports and built-in tool registry.
- Updated bash and task prompts plus start-result messaging to direct users to the `poll` tool.
- Added effective timeout metadata to Bash tool results and rendered output to show that effective timeout.
- Implemented bounded auto-background wait logic that backgrounds jobs when the timeout window is exhausted.
- Added `vim` as an edit variant in benchmark CLI/config and rating script coverage.
- Expanded benchmark execution so `vim` is treated as a mutation tool for retries, stats, and edit intent checks.
- Adjusted `TaskTool` output schema precedence so explicit params override agent frontmatter.
- Fixed `TaskTool` success counting by excluding aborted tasks from success totals.
- Improved validation guidance in `SubmitResultTool`/`TodoWriteTool` for clearer recovery when payloads are missing or invalid.
- Added background command PID regression coverage in `executeBash` to confirm a real, terminateable PID is returned.
- Fixed thinking configuration format by replacing `levels` array with `minLevel`/`maxLevel` properties across 100+ model definitions.
- Corrected GPT-5.4 mini/nano context window from 400000 to 272000 tokens for accurate token limit reporting.
- Normalized GPT-5.4 variant priority handling to use parsed variant instead of raw model IDs for consistent behavior.
- Added "mini" variant support to OpenAI model parsing regex and updated thinking mode configuration for Claude models.
- Fixed test robustness by replacing exact string matching with numeric range comparison to handle BSD seq notation on macOS.
- Corrected model generation script execution order to apply policy overrides before promotion target linking.
- Resolved symlinked paths before passing to brush shell to keep `pwd` output aligned with canonical Git worktree paths.
- Added `resolveShellCwd` helper that safely resolves symlinks and falls back to original path on error.
- Added test case verifying symlinked directories are canonicalized before execution.
The non-interactive environment variables (pager, editor, and prompt
suppression) were only applied in the PTY (interactive) bash path.
The non-PTY executeBash path had no such defaults, so commands could
block on pagers or credential prompts.
- Extract NO_PAGER_ENV from bash-interactive.ts into shared
non-interactive-env.ts module (renamed to NON_INTERACTIVE_ENV)
- Apply it in executeBash (non-PTY) with user overrides taking
precedence
- Fix ordering in interactive PTY path so user env overrides
the defaults (was reversed)
- Add test verifying defaults are applied alongside user env
- Consolidated truncation and output utilities from tools/truncate.ts and tools/output-utils.ts into session/streaming-output.ts with improved UTF-8 boundary handling.
- Renamed formatSize() to formatBytes() across codebase for consistency and clarity in byte-level formatting.
- Refactored OutputSink to use windowed byte truncation instead of full-buffer encoding, improving memory efficiency on large outputs.
- Migrated from Buffer to Uint8Array in web scrapers for better cross-platform compatibility and native browser support.
- Added getArtifactManager() lazy-initialization method to ToolSession for deferred artifact manager instantiation.
- Simplified API surface with wildcard exports from tools and session modules, reducing import complexity.
- Fixed persistent shell session state not being reset after command abort or hard timeout.
- Fixed hard timeout handling to properly interrupt long-running commands exceeding grace period.
- Introduced hard timeout mechanism with Promise.race() to enforce absolute timeout limit and prevent command hangs.
- Replaced shell command execution with explicit timeout and SIGKILL signal handling in shell-snapshot.
- Simplified bash command normalization to use only explicit head/tail parameters from tool input.
- Exported getAntigravityUserAgent() function for centralized User-Agent header construction.
- Fixed bash tool hanging with background jobs by implementing timeout-based cancellation and draining stdout/stderr pipes instead of waiting indefinitely for EOF.
- Fixed crash when auto-reading large @mentions by adding file size validation (5MB for text, 25MB for images) before reading file contents.
- Prevented out-of-memory issues by skipping files exceeding size limits and displaying file size information in UI for skipped files.
- Added GB file size formatting support to handle very large files in human-readable format.
- Added file size display in UI for skipped files to inform users why files were not included.
- Added test case verifying bash executor returns correctly when command spawns background jobs.
- Added Windows-specific process handle duplication mechanism to safely manage process termination across multiple handles.
- Implemented Windows signal handling support with Signal enum variants (Terminate, Kill, Interrupt) and kill_process() function using Windows API.
- Added background job termination on cancellation with platform-specific implementations (TERM/KILL signals on Unix, TerminateProcess on Windows).
- Implemented cancellation token support in shell execution with proper cleanup of background jobs when timeout or abort occurs.
- Added test coverage for background job termination on timeout and abort scenarios.
- Migrated all environment variable access from Node.js `process.env` to Bun runtime `Bun.env` API across the entire codebase.
- Updated 71 files including source code, tests, documentation, and configuration to use Bun's native environment variable API.
- Added comprehensive environment variables documentation in `packages/coding-agent/docs/environment-variables.md` covering 100+ environment variables organized by category.
- Updated CHANGELOG entries to reflect migration from `process.env` to `Bun.env` and environment variable prefix changes (PI_* vs OMP_*).
- Maintained identical behavior and logic across all changes - only the environment variable access method was updated.
- Migrated environment variable access from direct process.env to centralized getEnv() utility function across all packages.
- Renamed environment variable prefix from OMP_ to PI_ throughout codebase (e.g., OMP_CODING_AGENT_DIR -> PI_CODING_AGENT_DIR).
- Removed automatic environment variable migration from PI_ to OMP_ prefixes via migrate-env.ts module.
- Removed env setting from configuration schema and applyEnvironmentVariables() method from settings.
- Updated CI/CD build configuration to use PI_COMPILED flag instead of OMP_COMPILED.
- Changed venvPath property in PythonRuntime from nullable (string | null) to optional (string | undefined).
- Vendored brush-core crate locally to enable local development and patching.
- Added patch.crates-io section in Cargo.toml to redirect brush-core dependency to vendored version at crates/brush-core-vendored.
- Updated pi-natives Cargo.toml to use local path dependency for brush-core instead of external crate version.
- Added session-based shell execution with persistent shell instances and session management via sessionKey parameter.
- Added support for shell snapshots to initialize bash sessions with saved state via snapshotPath parameter.
- Added sessionEnv option to set environment variables once per session, separate from command-specific env variables.
- Added process group management functions (process_group_id and kill_process_group) for better process lifecycle control on Linux, macOS, and Windows.
- Changed env option to apply variables for the current command only, with session-level variables now managed via sessionEnv.
- Refactored shell execution to use async output queuing with enqueueChunk for proper chunk sequencing and improved timeout handling with process group termination.