The global test-setup.ts (preloaded via bunfig.toml) already snapshots
process.env in beforeEach and restores it in afterEach for every test.
Per-file env tracking is duplicate work and creates four different
patterns for the same problem.
Affected files:
- src/shared/claude-config-dir.test.ts (beforeEach/afterEach pair)
- src/shared/plugin-command-discovery.test.ts (ENV_KEYS + envSnapshot)
- src/features/claude-code-agent-loader/loader.test.ts (try/finally)
- src/features/skill-mcp-manager/connection-env-vars.test.ts (ORIGINAL_ENV)
In OpenCode web multi-workspace mode, skill_mcp spawned stdio child
processes inherited process.cwd() instead of the session's workspace
directory. Pass toolContext.directory through SkillMcpClientInfo to
StdioClientTransport's cwd parameter.
Added 'trusted' option to expandEnvVars. Skill MCPs are user-controlled
and now bypass the security allowlist. 3 files changed.
TDD verified. tsc clean.
Closes#3168
Bug fixes:
1. OAuth token refresh (#3149): buildHttpRequestInit() now attempts silent refresh
via refresh_token before triggering full browser re-auth. Added refresh() method
to McpOAuthProvider. Includes test isolation fix for discovery mock.
2. Quota error STOP (#3126): Added STOP_MESSAGE_PATTERNS in model-error-classifier
that take precedence over RETRYABLE_MESSAGE_PATTERNS. Message-only quota errors
now non-retryable. Runtime-fallback: quota_exceeded with 'retrying in' signal
still triggers fallback (provider-managed auto-retry). Restored removed patterns.
3. Compaction loop (#3127): MAX_RECOVERY_ATTEMPTS=3 cap + additional suppression
guard from opencode session in degradation monitor.
Also: refactored extractAutoRetrySignal to auto-retry-signal.ts, new regression
tests for quota classifier and compaction degradation monitor.
- Add afterAll(() => { mock.restore() }) to 52 test files missing cleanup
- Rewrite create-tool-guard-hooks.test.ts to use spyOn instead of barrel mock
- Fix skill-mcp-manager OAuth tests with missing mockTokens/mockLogin definitions
- Fix start-work hook: show worktree active block on resume with existing worktree_path
- Extract createWorktreeActiveBlock to worktree-block.ts to avoid circular import
- Replace 80-line isolated test runner CI config with single `bun test` command
Apply env filtering after customEnv merge to prevent filtered
variables (API keys, secrets) from being reintroduced through
custom environment configuration.
- MCP teardown race: add shutdownGeneration counter to prevent
in-flight connections from resurrecting after disconnectAll
- MCP multi-key disconnect race: replace disconnectedSessions Set
with generation-based Map to track per-session disconnect events
- MCP clients: check shutdownGeneration in stdio/http client
creators before inserting into state.clients
- BackgroundManager: call clearTaskHistoryWhenParentTasksGone after
timer-based task removal in scheduleTaskRemoval and notifyParentSession
- BackgroundManager: clean completedTaskSummaries when parent has
no remaining tasks
- Plugin dispose: remove duplicate tmuxSessionManager.cleanup call
since BackgroundManager.shutdown already handles it via onShutdown
H7: Process 'exit'/'SIGINT' listeners registered per-session were
never removed when all sessions disconnected, accumulating handlers.
- Add unregisterProcessCleanup() called in disconnectAll()
H8: Race condition where disconnectSession() during pending connection
left orphan clients in state.clients.
- Add disconnectedSessions Set to track mid-flight disconnects
- Check disconnect marker after connection resolves, close if stale
- Clear marker on reconnection for same session
Tests: 6 pass (3 disconnect + 3 race)
* fix: prevent zombie processes with proper process lifecycle management
- Await proc.exited for fire-and-forget spawns in tmux-utils.ts
- Remove competing process.exit() calls from LSP client and skill-mcp-manager
signal handlers to let background-agent manager coordinate final exit
- Await process exit after kill() in interactive-bash timeout handler
- Await process exit after kill() in LSP client stop() method
These changes ensure spawned processes are properly reaped and prevent
orphan/zombie processes when running with tmux integration.
* fix: address Copilot review comments on process cleanup
- LSP cleanup: use async/sync split with Promise.allSettled for proper subprocess cleanup
- LSP stop(): make idempotent by nulling proc before await to prevent race conditions
- Interactive-bash timeout: use .then()/.catch() pattern instead of async callback to avoid unhandled rejections
- Skill-mcp-manager: use void+catch pattern for fire-and-forget signal handlers
* fix: address remaining Copilot review comments
- interactive-bash: reject timeout immediately, fire-and-forget zombie cleanup
- skill-mcp-manager: update comments to accurately describe signal handling strategy
* fix: address additional Copilot review comments
- LSP stop(): add 5s timeout to prevent indefinite hang on stuck processes
- tmux-utils: log warnings when pane title setting fails (both spawn/replace)
- BackgroundManager: delay process.exit() to next tick via setImmediate to allow other signal handlers to complete cleanup
* fix: address code review findings
- Increase exit delay from setImmediate to 100ms setTimeout to allow async cleanup
- Use asyncCleanup for SIGBREAK on Windows for consistency with SIGINT/SIGTERM
- Add try/catch around stderr read in spawnTmuxPane for consistency with replaceTmuxPane
* fix: address latest Copilot review comments
- LSP stop(): properly clear timeout when proc.exited wins the race
- BackgroundManager: use process.exitCode before delayed exit for cleaner shutdown
- spawnTmuxPane: remove redundant log import, reuse existing one
* fix: address latest Copilot review comments
- LSP stop(): escalate to SIGKILL on timeout, add logging
- tmux spawnTmuxPane/replaceTmuxPane: drain stderr immediately to avoid backpressure
* fix: address latest Copilot review comments
- Add .catch() to asyncCleanup() signal handlers to prevent unhandled rejections
- Await proc.exited after SIGKILL with 1s timeout to confirm termination
* fix: increase exit delay to 6s to accommodate LSP cleanup
LSP cleanup can take up to 5s (timeout) + 1s (SIGKILL wait), so the exit
delay must be at least 6s to ensure child processes are properly reaped.
- Added withOperationRetry<T>() helper method that retries operations up to 3 times
- Catches "Not connected" errors (case-insensitive)
- Cleans up stale client before retry
- Modified callTool, readResource, getPrompt to use retry logic
- Added tests for retry behavior (3 new test cases)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Add mocks for HTTP transport to avoid real network calls during tests.
This addresses reviewer feedback about test reliability:
- Tests are now faster (no network latency)
- Tests are deterministic across environments
- Test intent is clearer (unit testing error handling logic)
The mock throws immediately with a controlled error message,
allowing tests to validate error handling without network dependencies.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>