- Replaced time-based sleeps and polling loops with event-driven promise resolvers and fake timers across agent and tool tests.
- Migrated test suites to share in-memory auth storage and fixtures using lifecycle hooks.
- Updated catalog model definitions, metadata, and configurations.
The postmortem module bound the native hard-exit once at module init
(process.reallyExit.bind(process)). The shipped bundle defers this
module's evaluation until first access, which can land inside a
withHostGuard window where process.reallyExit is the ExtensionExitError-
throwing stub; .bind() then froze that stub permanently, so every later
host-owned exit (SIGHUP 129, SIGINT 130, fatal 1) threw and re-entered
the unhandled-rejection fatal path in a loop (exit 129 storm).
Resolve the native exit on every call instead of binding at init, and
have withHostGuard stamp its throwing replacement with the native
primitive it shadows so a signal arriving mid-guard still exits (#6488)
without the guard poisoning later exits (#7393).
Fixes#7393
Routed postmortem.quit through the mutable process.exit path while retaining captured native exits for host-owned shutdown.
Added child-process coverage proving guarded postmortem.quit calls surface ExtensionExitError without terminating the host.
Fixes#6488
- Captured the native hard-exit function for postmortem signal, fatal, and manual exits.
- Added bounded child-process coverage for SIGINT and fatal cleanup during pending guarded loads.
- Preserved extension and hook exit isolation and made the EPIPE race assertion observe the real exit event.
Fixes#6488
Custom tool/extension/hook/plugin modules under ~/.claude/tools are
evaluated with live side effects during createAgentSession. A module that
attaches a stdin consumer at import time — an MCP StdioServerTransport
built at module top level, or a bare process.stdin.resume() — steals
Bun's single stdin reader, so the TUI receives exactly one data event and
goes permanently deaf after the first keypress. Under tmux the terminal's
automatic DA1 reply is that one event, so the first user keystroke is
already dead: the input-deafness reported in #5378/#5618.
Broadened the loader's withExitGuard (renamed withHostGuard) to also
snapshot and restore process.stdin around third-party module evaluation:
any data/readable/end/close/error listener the module adds is removed, and
the stream's paused and raw-mode state is restored to the pre-load
snapshot. The exit guard already fenced process.exit; stdin is the same
class of host-state hijack.
Fixes#5618
- Introduced guest snapshot reconciliation to maintain host state consistency during session switching.
- Improved yield tool reliability by implementing incremental schema validation and strict parameter enforcement.
- Fixed a calculation edge case in the status line to prevent negative time values during activity tracking.
- Expanded the test suite with new validation for session interruption, collab state synchronization, and process error handling.
Codex discovery surfaced every `~/.codex/hooks/*.{ts,js}` file as an OMP
hook (silently defaulting untyped names to `pre:<basename>`), and
`discoverExtensionPaths` then handed those paths to `loadExtension` for
dynamic import. A standalone Codex hook script with a top-level
`process.exit(0)` terminated the host CLI cleanly — `try/catch` around
`await import()` cannot intercept a synchronous exit, so OMP died at the
`loadExtensions:start` startup marker with no error surface.
Two-layer fix:
- `packages/coding-agent/src/extensibility/utils.ts`: new
`withExitGuard` helper patches `process.exit` for the duration of a
guarded callback so an exit raises `ExtensionExitError` instead of
terminating the process; nested and concurrent guards restore correctly
via depth counter.
- `extensibility/extensions/loader.ts`, `extensibility/hooks/loader.ts`,
and `extensibility/plugins/manager.ts` wrap their dynamic-import sites
in `withExitGuard` so the existing per-module `try/catch` records the
intercepted exit as a load error and OMP keeps starting.
- `discovery/codex.ts:loadHooks` no longer treats arbitrary files as
OMP hooks: only `pre-<tool>.{ts,js}` and `post-<tool>.{ts,js}` are
registered. Files like `memory-bank-reminder.ts` are silently skipped
rather than imported as extension factories.
Adds regression coverage:
- `test/extension-loader-process-exit.test.ts` — `loadExtensions` /
`loadHooks` return errors and leave `process.exit` restored when a
module exits at import time; sibling modules still load.
- `test/discovery/codex-hooks-discovery.test.ts` — codex provider
registers `pre-*` / `post-*` files and drops everything else.
Fixes#3680