Skip to content

feat: complete 0.9 runtime supervisor with leases, health tracking, adapters, and middleware - #25

Merged
aditzel merged 10 commits into
mainfrom
codex/0.9-runtime-identity
Jun 12, 2026
Merged

feat: complete 0.9 runtime supervisor with leases, health tracking, adapters, and middleware#25
aditzel merged 10 commits into
mainfrom
codex/0.9-runtime-identity

Conversation

@aditzel

@aditzel aditzel commented Jun 11, 2026

Copy link
Copy Markdown
Owner

Summary

Completes all remaining 0.9 runtime supervisor work items for dark-factory multi-agent operations:

  • Agent leasesAgentLeaseManager provides temporary exclusive access to singleton stdio servers with configurable TTL and automatic expiry. Non-holder calls queue until lease release.
  • Health trackingHealthTracker records upstream health state, restart counts, tool call metrics, and audit events with optional JSON persistence to ~/.config/mcp-squared/data/health.json.
  • Runtime adaptersUpstreamAdapter interface extracts transport-specific logic (stdio process spawning, HTTP transport creation) from the Cataloger into pluggable adapters with a registry.
  • mcporter SDK adapter — Factory for wrapping generated typed SDKs with MCP² policy and audit.
  • Proxy middlewareProxyMiddleware system with built-in session affinity, request logging, retry with exponential backoff, and auth injection middlewares.

Changes

New Files

  • src/runtime/agent-lease.ts — AgentLeaseManager
  • src/runtime/health-tracker.ts — HealthTracker
  • src/upstream/adapter.ts — UpstreamAdapter interface
  • src/upstream/adapters/stdio.ts — Stdio adapter
  • src/upstream/adapters/sse.ts — SSE/HTTP adapter
  • src/upstream/adapters/mcporter.ts — mcporter SDK adapter
  • src/upstream/adapter-registry.ts — Adapter registry
  • src/upstream/proxy-middleware.ts — ProxyMiddleware system

New Tests

  • tests/agent-lease.test.ts — 25 tests
  • tests/health-tracker.test.ts — 25 tests
  • tests/adapters.test.ts — 17 tests
  • tests/proxy-middleware.test.ts — 16 tests

Modified Files

  • src/config/schema.ts — Added HealthTrackingSchema and RuntimeLeaseSchema
  • src/config/paths.ts — Added getDataDir() and ensureDataDir()
  • src/runtime/supervisor.ts — Integrated lease and health tracking
  • docs/RUNTIME-SUPERVISOR.md — Marked all 0.9 items as done
  • CHANGELOG.md — Documented all new features

Verification

  • ✅ 1213 tests pass, 2 skipped, 0 failures
  • ✅ Build succeeds
  • ✅ Typecheck passes
  • ✅ Lint passes

Co-authored-by: MiMoCode Agent mimo@xiaomi.com

Summary by CodeRabbit

  • New Features

    • Added runtime supervision system for upstream servers with configurable lifecycle and concurrency policies.
    • Added health tracking and audit events for runtime monitoring and debugging.
    • Added OAuth-only SSE support with automatic token storage and reuse.
    • Added upstream adapter registry system for extensible transport handling.
    • Enhanced daemon/proxy reconnection with improved recovery and coordination.
  • Documentation

    • Updated architecture documentation with new Runtime Supervisor component.
    • Added Runtime Supervisor configuration guide with policy defaults by transport type.
    • Updated status command to display runtime policies in verbose mode.
  • Chores

    • Updated project version from 0.8.0 to 0.8.1.
    • Refactored CLI into modular runtime-dispatched architecture.
    • Updated Biome and dependencies.

aditzel and others added 7 commits March 11, 2026 15:00
Co-authored-by: Junie <junie@jetbrains.com>
Co-authored-by: Junie <junie@jetbrains.com>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Codex <codex@openai.com>
Phase 2 of the 0.9 runtime-supervisor plan:

- Thread daemon/proxy client identity into RuntimeCallContext via sessionId/agentId.

- Pass identity from shared daemon/proxy path down to Cataloger.callTool().

- Preserve current behavior for direct stdio mode where no identity exists.

- Add focused tests for session-affine serialization, agent-based locking, and no-context parallelism.

- Update docs/RUNTIME-SUPERVISOR.md and CHANGELOG.md.

Co-authored-by: Codex <codex@openai.com>
…dapters, and middleware

Implements the remaining 0.9 runtime supervisor work items:

- Agent lease system (AgentLeaseManager) for temporary exclusive access
  to singleton stdio servers with configurable TTL and automatic expiry
- Runtime health tracking (HealthTracker) for upstream health state,
  restart counts, and audit events with JSON persistence
- Runtime adapter system (UpstreamAdapter interface) with built-in
  stdio/HTTP adapters and adapter registry
- mcporter SDK adapter factory for wrapping generated typed SDKs
- Proxy middleware system (ProxyMiddleware) with session affinity,
  request logging, retry with exponential backoff, and auth injection

Co-authored-by: MiMoCode Agent <mimo@xiaomi.com>
@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@aditzel, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 24 minutes and 59 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2313d655-f01e-4228-abac-e1a50d75ba74

📥 Commits

Reviewing files that changed from the base of the PR and between b6325a4 and 3aadb1b.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • docs/DEPENDENCY_EXCEPTIONS.md
  • package.json
  • src/daemon/proxy.ts
  • tests/daemon-proxy.test.ts

Walkthrough

The PR updates project status and docs, adds runtime policy and supervision infrastructure, splits CLI and server orchestration into modules, introduces upstream adapters and daemon recovery, and expands tests across the new execution and lifecycle flows.

Changes

Runtime supervisor foundation

Layer / File(s) Summary
Version, docs, and config surface
README.md, WARP.md, CHANGELOG.md, docs/*, package.json, biome.json, .github/CONTRIBUTING.md, .gitignore, .junie/memory/*
Version labels, guidance docs, changelog entries, dependency notes, and config metadata are updated to reflect the runtime-supervisor work and current alpha status.
CLI runtime dispatch and entrypoints
src/index.ts, src/cli/*, src/config/paths.ts, src/config/index.ts, src/cli/tui-runtime.ts
The top-level CLI is routed through a shared dispatcher and per-command runners, with reusable process/bootstrap helpers and TUI missing-module handling.
Runtime policy, leases, health, and adapters
src/config/schema.ts, src/config/validate.ts, src/runtime/*, src/upstream/*
Upstream runtime policy, lease tracking, health persistence, runtime supervision, adapter registry/adapters, proxy middleware, upstream client reconnect handling, and daemon recovery logic are introduced or refactored together.
Capability surface and server lifecycle
src/server/*, src/status/runner.ts, src/init/runner.ts
Capability discovery/execution, session resources, server runtime lifecycle, server-shell orchestration, and verbose status reporting are split into dedicated helpers and delegated from the main server class.
Regression coverage for CLI, runtime, and daemon flows
tests/*
Tests cover the new CLI command runners, runtime bootstrap and supervision, adapter behavior, server capability execution, status formatting, and daemon/session recovery cases.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/0.9-runtime-identity

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b6325a4898

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/config/schema.ts
Comment on lines +86 to +90
lifecycle: RuntimeLifecycleModeSchema.optional(),
concurrency: RuntimeConcurrencyModeSchema.optional(),
maxPoolSize: z.number().int().min(1).optional(),
restart: RuntimeRestartPolicySchema.optional(),
lease: RuntimeLeaseSchema.optional(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Wire accepted runtime policy fields into execution

The schema now accepts lifecycle, maxPoolSize, restart, and lease, and status --verbose reports those values, but production execution only uses runtime.concurrency: McpSquaredServer still constructs a plain new Cataloger(), Cataloger.connect() creates exactly one SDK client/transport per upstream, and Cataloger.callTool() only passes calls through UpstreamCallSupervisor. In any config that sets lifecycle = "ephemeral"/"pooled", maxPoolSize, restart, or lease.enabled, the CLI will accept and display the policy while continuing to use the existing singleton connection with no restart or lease behavior.

Useful? React with 👍 / 👎.

Comment thread src/config/schema.ts
Comment on lines +484 to +487
healthTracking: HealthTrackingSchema.default({
enabled: false,
maxAuditEvents: 1000,
}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Hook health tracking config into the runtime

Setting [operations.healthTracking] enabled = true is now valid config, but no production path constructs a HealthTracker from this section or passes it to UpstreamCallSupervisor (the default server path creates new Cataloger() with no tracker). As a result, users can enable the advertised audit/health persistence and still get no persisted health state or tool-call audit events.

Useful? React with 👍 / 👎.

Comment thread src/daemon/proxy.ts
Comment on lines +139 to +147
if (options.configHash) {
const registry = await loadLiveDaemonRegistry(options.configHash);
if (registry) {
sharedSecret ??= registry.sharedSecret;
endpoint = registry.endpoint;
return registry.endpoint;
}

if (allowSpawn && !options.noSpawn) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Allow default proxy startup to spawn a daemon

When callers use the exported default runProxy()/createProxyBridge() path without passing either endpoint or configHash, the initial connectDaemon(true) reaches resolveDaemonTarget(true) but the spawn branch is nested under if (options.configHash), so it falls through to Daemon endpoint not available. The previous implementation spawned a daemon whenever no endpoint was found and noSpawn was false, so embedders using the default proxy API now fail before starting a daemon.

Useful? React with 👍 / 👎.

- Update smol-toml, hono, @hono/node-server, yaml to patched versions
- Add dependency exceptions for documented transitive vulnerabilities
  (protobufjs, hono, fast-uri, qs, ip-address, path-to-regexp)
- Configure bun audit to ignore documented GHSA IDs

Co-authored-by: MiMoCode Agent <mimo@xiaomi.com>
@socket-security

socket-security Bot commented Jun 11, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updated@​types/​bun@​1.3.9 ⏵ 1.3.101001004992 +1100
Updatedsmol-toml@​1.6.0 ⏵ 1.6.1100 +1100 +2100 +184100
Updated@​opentui/​core@​0.1.82 ⏵ 0.1.8785 -110092 +198 +1100
Updatedyaml@​2.8.2 ⏵ 2.9.0100 +1100 +210090100
Updatedtar@​7.5.10 ⏵ 7.5.1198100 +169992100
Added@​hono/​node-server@​1.19.1410010010094100
Updatedhono@​4.12.5 ⏵ 4.12.2599 +2100 +5097 +196100
Updated@​biomejs/​biome@​2.4.4 ⏵ 2.4.6100 +110010098100

View full report

@socket-security

socket-security Bot commented Jun 11, 2026

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Obfuscated code: npm @opentui/core is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: package.jsonnpm/@opentui/core@0.1.87

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@opentui/core@0.1.87. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

Tests were setting process.env['MCP_CLIENT_NAME'] which is global state,
causing race conditions when tests run in parallel. Added explicit
clientName option to ProxyBridgeOptions so tests pass client identity
directly without mutating environment variables.

Co-authored-by: MiMoCode Agent <mimo@xiaomi.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 19

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (2)
src/cli/run-stdio-server.ts-150-164 (1)

150-164: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clear the forced-exit timer after shutdown completes.

forceExitTimer is never cleared. If processRef.exit is mocked or non-terminating, the timer can still fire and issue a second exit(1).

Suggested fix
-    try {
-      const forceExitTimer = setTimeout(() => {
+    const forceExitTimer = setTimeout(() => {
         console.error("Forcing shutdown after timeout");
         processRef.exit(1);
-      }, 2000);
-      forceExitTimer.unref();
-
+    }, 2000);
+    forceExitTimer.unref();
+    try {
       await server.stop();
       registration.unregisterInstance();
+      clearTimeout(forceExitTimer);
       processRef.exit(exitCode);
     } catch (err) {
       const message = err instanceof Error ? err.message : String(err);
       console.error(`Error during shutdown: ${message}`);
       registration.unregisterInstance();
+      clearTimeout(forceExitTimer);
       processRef.exit(1);
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/cli/run-stdio-server.ts` around lines 150 - 164, The forced-exit timer
(forceExitTimer) is never cleared and can fire after a successful shutdown;
before calling processRef.exit(exitCode) in the try block clear it
(clearTimeout(forceExitTimer)), and also clear it in the catch block before
calling processRef.exit(1); update the shutdown flow around await server.stop(),
registration.unregisterInstance(), and both exit paths to ensure forceExitTimer
is cleared to prevent a late forced exit.
src/runtime/health-tracker.ts-196-203 (1)

196-203: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Return snapshots instead of live UpstreamHealth objects.

getHealth() and getAllHealth() currently expose the same mutable objects stored in this.upstreams, so any caller can accidentally rewrite counters or status and corrupt later persistence. Returning cloned snapshots avoids external mutation of tracker state.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/runtime/health-tracker.ts` around lines 196 - 203, getHealth and
getAllHealth currently return live UpstreamHealth objects from this.upstreams;
modify them to return cloned snapshots to prevent external mutation. In
getHealth(upstreamKey) return a copy of the stored object (e.g., clone the value
retrieved from this.upstreams.get and return null if missing) and in
getAllHealth() return Array.from(this.upstreams.values()).map(v => /* clone v
*/) so callers receive new objects (use a shallow/deep clone appropriate for
UpstreamHealth fields, e.g., object spread + cloning nested counters or
JSON.parse(JSON.stringify(...)) if safe). Ensure you reference getHealth,
getAllHealth and this.upstreams when making the changes.
🧹 Nitpick comments (6)
tests/server-handlers.test.ts (1)

353-353: ⚡ Quick win

Use explicit non-error state for success assertions.

Line 353 asserts isError is undefined; this weakens the success contract. Assert false to keep success/error state unambiguous.

Proposed assertion update
-    expect(describeResult.isError).toBeUndefined();
+    expect(describeResult.isError).toBe(false);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/server-handlers.test.ts` at line 353, The test currently asserts
describeResult.isError is undefined which is ambiguous; update the assertion to
explicitly assert success by checking describeResult.isError === false so the
success/error contract is unambiguous—locate the test where describeResult is
used (the failing expectation line with
expect(describeResult.isError).toBeUndefined()) and change it to assert false
instead.
tests/adapters.test.ts (1)

50-53: ⚡ Quick win

Strengthen the process.env fallback assertion.

Line 50-Line 53 only checks for "/test" suffix, which can pass even if fallback resolution is broken. Use a temporary unique env var and assert exact replacement.

Proposed test tightening
   test("falls back to process.env", () => {
-    const result = resolveEnvVars("$PATH/test", {});
-    expect(result).toContain("/test");
+    const prev = process.env.MCP2_TEST_FALLBACK;
+    process.env.MCP2_TEST_FALLBACK = "/tmp/mcp2-bin";
+    try {
+      const result = resolveEnvVars("$MCP2_TEST_FALLBACK/test", {});
+      expect(result).toBe("/tmp/mcp2-bin/test");
+    } finally {
+      if (prev === undefined) {
+        delete process.env.MCP2_TEST_FALLBACK;
+      } else {
+        process.env.MCP2_TEST_FALLBACK = prev;
+      }
+    }
   });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/adapters.test.ts` around lines 50 - 53, The test for resolveEnvVars
should use a unique temporary environment variable instead of relying on PATH;
set a unique key in process.env (e.g., UNIQUE_TEST_ENV) to a known value, call
resolveEnvVars("$UNIQUE_TEST_ENV/test", {}), and assert the returned string
equals the exact concatenation of that env value and "/test"; ensure the test
cleans up the temporary process.env entry afterwards. This targets the
resolveEnvVars function and the test named "falls back to process.env".
tests/run-proxy.test.ts (1)

98-98: ⚡ Quick win

Rename this test to match the asserted behavior.

The title says startup failure unregisters the proxy instance, but Line 130 asserts the opposite (deleteInstanceEntry is not called). Please align the name with the assertion to avoid future confusion.

Proposed rename
-  test("unregisters the proxy instance if startup fails", async () => {
+  test("does not unregister the proxy instance when startup fails before registration", async () => {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/run-proxy.test.ts` at line 98, The test title is misleading: it
currently reads "unregisters the proxy instance if startup fails" but the
assertion checks that deleteInstanceEntry is NOT called; update the test name
string in the test("...") for the test in tests/run-proxy.test.ts (the test that
references deleteInstanceEntry and asserts it was not called) to accurately
reflect the behavior, e.g. "does not unregister the proxy instance if startup
fails" or "does not call deleteInstanceEntry when startup fails".
src/config/schema.ts (1)

178-228: 💤 Low value

Consider simplifying nested lease config resolution.

The lease configuration resolution on lines 222-225 could be flattened for readability, though the current implementation is correct.

♻️ Optional simplification
   lease: {
-    enabled: config.runtime?.lease?.enabled ?? defaults.lease.enabled,
-    maxDurationMs:
-      config.runtime?.lease?.maxDurationMs ?? defaults.lease.maxDurationMs,
+    enabled: config.runtime?.lease?.enabled ?? defaults.lease.enabled,
+    maxDurationMs: config.runtime?.lease?.maxDurationMs ?? defaults.lease.maxDurationMs,
   },

Or consider using a helper:

lease: {
  ...defaults.lease,
  ...(config.runtime?.lease ?? {}),
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/config/schema.ts` around lines 178 - 228, The lease resolution in
resolveUpstreamRuntimeDefaults is correct but can be simplified for readability:
replace the explicit field-by-field merge of lease (currently using
config.runtime?.lease?.enabled and maxDurationMs with defaults.lease.*) with a
shallow merge that spreads defaults.lease first and then overrides with
config.runtime?.lease (e.g., lease: { ...defaults.lease,
...(config.runtime?.lease ?? {}) }); this keeps behavior identical while
flattening logic referencing
DEFAULT_STDIO_RUNTIME_CONFIG/DEFAULT_SSE_RUNTIME_CONFIG and
config.runtime?.lease.
src/upstream/adapters/stdio.ts (1)

58-77: 💤 Low value

Hardcoded Bun.sleep(100) limits portability and lacks justification.

Line 63 uses Bun.sleep(100), which:

  1. Ties the code to the Bun runtime (not portable to Node.js)
  2. Uses an arbitrary 100ms delay without explanation

If cross-runtime compatibility is needed, consider a platform-agnostic sleep helper. The delay duration should also be documented or made configurable.

♻️ Platform-agnostic sleep helper
// Add platform-agnostic sleep utility
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));

async function postStdioConnect(
  _context: AdapterContext,
  client: Client,
): Promise<PostConnectResult> {
  // Give the process a moment to fail if it's going to (100ms chosen empirically)
  await sleep(100);
  
  // ... rest of function
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/upstream/adapters/stdio.ts` around lines 58 - 77, The postStdioConnect
function currently uses Bun.sleep(100), which couples the code to Bun and hides
why 100ms was chosen; replace Bun.sleep(100) with a platform-agnostic sleep
helper (e.g., const sleep = (ms:number)=>new Promise(r=>setTimeout(r,ms))) and
call await sleep(delayMs) inside postStdioConnect instead of Bun.sleep; make
delayMs configurable (via parameter, constant, or config value) and document the
chosen default (e.g., 100ms) where the helper or postStdioConnect is defined so
the runtime is portable and the timeout rationale is clear.
src/status/runner.ts (1)

53-53: 💤 Low value

Redundant | undefined with optional operator.

The ? operator already makes the field undefined when absent, so | undefined is redundant (though not incorrect).

♻️ Simplification
-  runtime?: EffectiveUpstreamRuntimeConfig | undefined;
+  runtime?: EffectiveUpstreamRuntimeConfig;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/status/runner.ts` at line 53, The field declaration runtime?:
EffectiveUpstreamRuntimeConfig | undefined redundantly unions with undefined;
remove the "| undefined" so the property reads runtime?:
EffectiveUpstreamRuntimeConfig to rely on the optional operator. Update the
declaration in the same interface/type where runtime is defined (reference
symbol: runtime) and run a quick TypeScript typecheck to ensure no other code
relied on the explicit union.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/DEPENDENCY_EXCEPTIONS.md`:
- Line 7: The exception entry for the `file-type` infinite loop is overdue;
update the `Review By` date in DEPENDENCY_EXCEPTIONS.md (the table row
referencing `@opentui/core -> jimp -> `@jimp/core` -> file-type`) to a new future
review deadline, replace "Pending maintainer tracking issue" with a concrete
issue/PR reference or tracker ID, and add a short note in the same table cell
stating who is responsible for follow-up (e.g., maintainer GitHub handle or
team) and the action expected (reassess on `@opentui/core` or `jimp` bumps and
before routing untrusted binaries through TUI).

In `@src/cli/main-runtime.ts`:
- Around line 54-55: The isStderrTty property is incorrectly wired to
process.stdout.isTTY; update the assignment so isStderrTty reads
process.stderr.isTTY instead to reflect the actual stderr TTY state (modify the
object/initializer where isStderrTty and isStdinTty are set in main-runtime.ts).

In `@src/cli/run-auth.ts`:
- Around line 251-255: The callback currently only checks state when
result.state is present, allowing missing state to bypass CSRF protection;
update the validation in the callback handler (where result.state and
authProvider.verifyState are used) to treat a missing state as an error: if
result.state is falsy OR authProvider.verifyState(result.state) returns false,
log the OAuth state mismatch and exit (processRef.exit(1)) just as when
verification fails.

In `@src/cli/run-daemon.ts`:
- Around line 105-107: The registry entry currently always uses
monitorSocketPath instead of the effective daemon socket path; update the
instance registration to use the final socket path value from daemonOptions
(taking into account options.socketPath override) rather than the original
monitorSocketPath. Locate the code that builds the instance/registry entry (the
block that references monitorSocketPath) and replace that field with the
resolved socket path from daemonOptions.socketPath (or the variable that holds
the effective daemon socket) so consumers get the correct endpoint.

In `@src/cli/runtime-bootstrap.ts`:
- Around line 93-113: The shutdown hook can be invoked multiple times
(SIGINT/SIGTERM and stdin 'end'/'close'), so wrap calls to shutdown in a
one-time guard: inside registerShutdownHooks create a local boolean (e.g.,
"shutdownCalled") and a small wrapper function (e.g., "invokeShutdown(code)")
that returns immediately if shutdownCalled is true, sets it true on first call,
then calls void shutdown(code); replace all direct shutdown(...) calls (the
signal handlers and stdin handlers) with this wrapper so cleanup runs only once;
alternatively use processRef.once for signals but still protect stdin handlers
with the same one-time guard to avoid duplicate teardown.

In `@src/daemon/proxy.ts`:
- Around line 325-337: The stdioTransport.onmessage handler currently drops
messages when daemonTransport is null or send() rejects; change it to buffer
outbound messages (e.g., in a queue) while daemon is disconnected and only
drain/flush the queue after connectDaemon()/reconnectDaemon() completes
successfully, or alternatively fail the stdio side immediately on permanent
errors so callers can retry. Specifically, introduce a message queue and enqueue
messages inside stdioTransport.onmessage when daemonTransport is falsy or when
activeTransport.send() rejects; trigger reconnectDaemon() as before but do not
discard messages, and on successful connectDaemon()/reconnectDaemon() drain the
queue by calling daemonTransport.send() for each item (with appropriate
backpressure/retry/failure handling that will respond to stdio if sends
permanently fail). Ensure logic still respects stopping, stdioClosed, and
canRecoverDaemon flags and use existing functions/variables:
stdioTransport.onmessage, daemonTransport, reconnectDaemon, connectDaemon,
activeTransport.send, stopping, stdioClosed, canRecoverDaemon.

In `@src/daemon/server.ts`:
- Around line 345-349: The code is trusting caller-supplied session.clientId and
promotes it to runtime identity and eviction logic; instead stop using
session.clientId as agentId in getRuntimeCallContext and eviction; bind agent
identity to a server-issued/authenticated value stored on the server-side
session (e.g., session.agentId or session.authenticatedAgentId populated during
authentication when the session is created/validated) and use that for
sessionServer/createSessionServer getRuntimeCallContext and any eviction logic;
remove any use of the incoming message payload (session.clientId) for identity
or eviction (also update the logic in the related block around the 402-421
range) so only server-authenticated identifiers determine agent identity and
session-affinity.

In `@src/runtime/health-tracker.ts`:
- Around line 237-246: Parse the file content into unknown instead of casting to
HealthStore, validate that the top-level object has version === 1, that
store.upstreams is an object whose entries have the expected Health shape before
calling this.upstreams.set, and that store.auditEvents is an array of valid
audit event objects (each containing a numeric id); only adopt validated entries
into this.upstreams and this.auditEvents. Replace the current eventIdCounter =
this.auditEvents.length with logic that computes the next id as (max(existing
audit event id) + 1) or 1 if none. Update places referenced in this file (e.g.,
the variables store, this.upstreams, this.auditEvents, this.eventIdCounter and
methods getAuditEvents()/save()) to rely on the validated data. Ensure failures
in validation bail out without mutating internal state and log or throw a clear
error so corrupted health.json is not adopted.

In `@src/runtime/supervisor.ts`:
- Around line 72-74: The current run() path skips lease enforcement when
context.agentId is missing and getLockKey() can return null for session_affine
without identity, allowing concurrent access; modify run() and
runLeaseAware()/getLockKey() so a missing identity is treated as a non-holder
(i.e., still subject to lease waits) and when session_affine yields neither
sessionId nor agentId, return an upstream-wide lockKey instead of null; update
logic around this.leaseManager, Supervisor.run(), Supervisor.runLeaseAware(),
and getLockKey()/session_affine handling to enforce leases and fall back to
upstreamKey when no identity exists.

In `@src/server/capability-tool-executor.ts`:
- Around line 42-44: The current check returns any object with a "type" field as
text by force-casting "entry" to { type: "text"; text: string }, which lets
non-text blocks and invalid text values leak through; update the condition to
explicitly verify entry.type === "text" and that typeof entry.text === "string"
(and entry !== null), then return entry cast to the text shape only in that
case, otherwise return the non-text path (e.g., undefined/null or fallthrough)
so the function's text-only contract is preserved.

In `@src/server/capability-tool-surface.ts`:
- Around line 276-280: The code uses parsedRequest.action or legacy aliases as
the forwarded policyAction which lets alias names bypass canonical policy
checks; find where exactRoute is computed (the liveRouter.actions find using
parsedRequest.action) and change the policyAction to use the canonical route
action (exactRoute.action) instead of parsedRequest.action or its aliases, with
a safe fallback to parsedRequest.action only if exactRoute is undefined; update
the forwarding site that sets policyAction (the code around the policyAction
variable/argument at the block forwarding lines ~325-328) to reference
exactRoute.action.

In `@src/server/server-shell.ts`:
- Around line 31-39: The session lifecycle can leak core runtime state if
args.server.connect throws or if args.server.close fails: wrap the connect
sequence (calls to startCore, registerConfiguredSessionSurface, new
StdioServerTransport, args.server.connect, and
args.statsCollector.incrementActiveConnections) in try/catch/finally so that a
failed connect will call args.stopCore() and undo any partial registration;
likewise, wrap shutdown (args.server.close and
args.statsCollector.decrementActiveConnections) so that a failed close still
calls args.stopCore(), and ensure stats increment/decrement are only applied on
successful connect/close (use try { await args.server.connect(...) ;
args.statsCollector.incrementActiveConnections(); } catch (e) { await
args.stopCore(); throw e } and a similar pattern around args.server.close to
always await args.stopCore() in a finally block).

In `@src/upstream/adapters/stdio.ts`:
- Around line 24-52: The merge of process.env with resolvedEnv in
createStdioTransport uses an unsafe cast to Record<string,string> which can
allow undefined values into params.env; replace that cast by building a safe env
object (for example merge process.env and resolvedEnv into a plain object then
filter out entries whose value is undefined) and pass that filtered
Record<string,string> to StdioClientTransport via params.env so
StdioClientTransport never receives undefined env values.

In `@src/upstream/cataloger.ts`:
- Around line 634-653: The thunk passed to callSupervisor.run currently closes
over the outer `client` and `connection`, which can become stale; update the
lambda passed to `this.callSupervisor.run(...)` to re-resolve the live
connection from `this.connections.get(result.tool.serverKey)` and fetch
`connection.client` inside the callback, check `connection` exists and
`connection.status === "connected"` (and that `client` is present), and if not,
throw/return an appropriate error to abort the call; keep usage of
`parseQualifiedName`/`bareToolName`, `args`, and `context` the same but ensure
the actual remote call uses the re-read `connection.client` rather than the
outer `client`.

In `@src/upstream/client.ts`:
- Around line 345-358: The retry is still racing client.connect(transport)
against the original timeoutPromise created before the OAuth flow, so recreate a
fresh timeout promise for the post-OAuth connect attempt; before awaiting
Promise.race([client.connect(transport!), timeoutPromise]) in the retry block,
build a new timeoutPromise (using the same timeoutMs/timeout creation logic used
originally) so that each connect() call races against a newly-started timeout;
locate this in the retry branch around safelyCloseTransport/
httpTransportFactory/transport and replace the reused timeoutPromise with the
newly created one.

In `@src/upstream/proxy-middleware.ts`:
- Around line 204-214: The retry detection currently in the middleware fn
(checking ctx.response?.error, retryCount, maxRetries and setting
ctx.state["shouldRetry"]/["retryDelayMs"]) only runs in the "after_call" phase
per options.phases; register this middleware for the error phase as well or move
the retry-detection logic into the on_error handler so transport failures set
ctx.state["shouldRetry"] and ctx.state["retryDelayMs"]. Update the
options.phases to include "on_error" (or relocate the fn's logic to the on_error
hook) so functions using on_error participate in backoff, keeping the same
checks for retryCount, maxRetries, baseDelayMs and state keys.
- Around line 130-145: The sessionMap used by the "session-affinity" middleware
(symbol: sessionMap, middleware name "session-affinity", handler fn) never
evicts entries causing unbounded memory growth; implement eviction by either
replacing sessionMap with an LRU/TTL cache (use an LRU cache library or a Map
keyed to sessionId with stored timestamps and a max size/ttl) and run periodic
cleanup, or add a teardown hook tied to session lifecycle (e.g., remove
sessionMap entry when ctx.callContext signals session end) so entries are
removed when sessions terminate; ensure lookups still use
sessionMap.get(sessionId) and updates use sessionMap.set(sessionId,
ctx.upstreamKey).

In `@tests/daemon-proxy.test.ts`:
- Around line 403-411: Replace the fixed 250ms sleep with a condition-based wait
that polls until the reconnection is observed: after calling await
secondDaemon.start(), loop or use a waitFor helper to repeatedly call await
client.listTools() (or reuse recoveredTools logic) and check that the returned
tool names include "time_util", with a sensible timeout (e.g., few seconds) and
short interval between attempts; update the block around firstDaemon,
secondDaemon, and client.listTools() so the test only proceeds when the
recovered tool condition is met instead of awaiting a hardcoded setTimeout.
- Around line 560-570: The test mutates process.env["MCP_CLIENT_NAME"] multiple
times (e.g., around the createProxyBridge calls) without restoring it, risking
cross-test leakage; update each mutation site (the assignments before
createProxyBridge invocations) to capture the previous value, set the new value
for the duration of that setup, and then restore the original value after the
bridge is created (or use try/finally) so MCP_CLIENT_NAME is returned to its
prior value; references to update include the MCP_CLIENT_NAME env key and the
createProxyBridge usages in the test file so you can locate and wrap each
mutation (also apply the same restore pattern at the other listed locations).

---

Minor comments:
In `@src/cli/run-stdio-server.ts`:
- Around line 150-164: The forced-exit timer (forceExitTimer) is never cleared
and can fire after a successful shutdown; before calling
processRef.exit(exitCode) in the try block clear it
(clearTimeout(forceExitTimer)), and also clear it in the catch block before
calling processRef.exit(1); update the shutdown flow around await server.stop(),
registration.unregisterInstance(), and both exit paths to ensure forceExitTimer
is cleared to prevent a late forced exit.

In `@src/runtime/health-tracker.ts`:
- Around line 196-203: getHealth and getAllHealth currently return live
UpstreamHealth objects from this.upstreams; modify them to return cloned
snapshots to prevent external mutation. In getHealth(upstreamKey) return a copy
of the stored object (e.g., clone the value retrieved from this.upstreams.get
and return null if missing) and in getAllHealth() return
Array.from(this.upstreams.values()).map(v => /* clone v */) so callers receive
new objects (use a shallow/deep clone appropriate for UpstreamHealth fields,
e.g., object spread + cloning nested counters or JSON.parse(JSON.stringify(...))
if safe). Ensure you reference getHealth, getAllHealth and this.upstreams when
making the changes.

---

Nitpick comments:
In `@src/config/schema.ts`:
- Around line 178-228: The lease resolution in resolveUpstreamRuntimeDefaults is
correct but can be simplified for readability: replace the explicit
field-by-field merge of lease (currently using config.runtime?.lease?.enabled
and maxDurationMs with defaults.lease.*) with a shallow merge that spreads
defaults.lease first and then overrides with config.runtime?.lease (e.g., lease:
{ ...defaults.lease, ...(config.runtime?.lease ?? {}) }); this keeps behavior
identical while flattening logic referencing
DEFAULT_STDIO_RUNTIME_CONFIG/DEFAULT_SSE_RUNTIME_CONFIG and
config.runtime?.lease.

In `@src/status/runner.ts`:
- Line 53: The field declaration runtime?: EffectiveUpstreamRuntimeConfig |
undefined redundantly unions with undefined; remove the "| undefined" so the
property reads runtime?: EffectiveUpstreamRuntimeConfig to rely on the optional
operator. Update the declaration in the same interface/type where runtime is
defined (reference symbol: runtime) and run a quick TypeScript typecheck to
ensure no other code relied on the explicit union.

In `@src/upstream/adapters/stdio.ts`:
- Around line 58-77: The postStdioConnect function currently uses
Bun.sleep(100), which couples the code to Bun and hides why 100ms was chosen;
replace Bun.sleep(100) with a platform-agnostic sleep helper (e.g., const sleep
= (ms:number)=>new Promise(r=>setTimeout(r,ms))) and call await sleep(delayMs)
inside postStdioConnect instead of Bun.sleep; make delayMs configurable (via
parameter, constant, or config value) and document the chosen default (e.g.,
100ms) where the helper or postStdioConnect is defined so the runtime is
portable and the timeout rationale is clear.

In `@tests/adapters.test.ts`:
- Around line 50-53: The test for resolveEnvVars should use a unique temporary
environment variable instead of relying on PATH; set a unique key in process.env
(e.g., UNIQUE_TEST_ENV) to a known value, call
resolveEnvVars("$UNIQUE_TEST_ENV/test", {}), and assert the returned string
equals the exact concatenation of that env value and "/test"; ensure the test
cleans up the temporary process.env entry afterwards. This targets the
resolveEnvVars function and the test named "falls back to process.env".

In `@tests/run-proxy.test.ts`:
- Line 98: The test title is misleading: it currently reads "unregisters the
proxy instance if startup fails" but the assertion checks that
deleteInstanceEntry is NOT called; update the test name string in the
test("...") for the test in tests/run-proxy.test.ts (the test that references
deleteInstanceEntry and asserts it was not called) to accurately reflect the
behavior, e.g. "does not unregister the proxy instance if startup fails" or
"does not call deleteInstanceEntry when startup fails".

In `@tests/server-handlers.test.ts`:
- Line 353: The test currently asserts describeResult.isError is undefined which
is ambiguous; update the assertion to explicitly assert success by checking
describeResult.isError === false so the success/error contract is
unambiguous—locate the test where describeResult is used (the failing
expectation line with expect(describeResult.isError).toBeUndefined()) and change
it to assert false instead.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b951b31c-ee7f-42c3-9aa7-d614a8b75a06

📥 Commits

Reviewing files that changed from the base of the PR and between 41144bf and b6325a4.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (86)
  • .github/CONTRIBUTING.md
  • .gitignore
  • .junie/memory/errors.md
  • .junie/memory/feedback.md
  • .junie/memory/language.json
  • .junie/memory/memory.version
  • .junie/memory/tasks.md
  • CHANGELOG.md
  • README.md
  • WARP.md
  • biome.json
  • docs/ARCHITECTURE.md
  • docs/DEPENDENCY_EXCEPTIONS.md
  • docs/DEPENDENCY_MAINTENANCE.md
  • docs/PROJECT-INCEPTION-REQUIREMENTS.md
  • docs/RELEASING.md
  • docs/RUNTIME-SUPERVISOR.md
  • package.json
  • src/cli/main-runtime.ts
  • src/cli/run-auth.ts
  • src/cli/run-daemon.ts
  • src/cli/run-import.ts
  • src/cli/run-install.ts
  • src/cli/run-migrate.ts
  • src/cli/run-monitor.ts
  • src/cli/run-proxy.ts
  • src/cli/run-stdio-server.ts
  • src/cli/run-test.ts
  • src/cli/runtime-bootstrap.ts
  • src/cli/runtime-dispatch.ts
  • src/cli/runtime-profiles.ts
  • src/cli/tui-runtime.ts
  • src/config/index.ts
  • src/config/paths.ts
  • src/config/schema.ts
  • src/config/validate.ts
  • src/daemon/proxy.ts
  • src/daemon/server.ts
  • src/index.ts
  • src/init/runner.ts
  • src/runtime/agent-lease.ts
  • src/runtime/health-tracker.ts
  • src/runtime/supervisor.ts
  • src/server/capability-surface.ts
  • src/server/capability-tool-executor.ts
  • src/server/capability-tool-surface.ts
  • src/server/index.ts
  • src/server/runtime-lifecycle.ts
  • src/server/server-shell.ts
  • src/server/session-surface.ts
  • src/status/runner.ts
  • src/upstream/adapter-registry.ts
  • src/upstream/adapter.ts
  • src/upstream/adapters/mcporter.ts
  • src/upstream/adapters/sse.ts
  • src/upstream/adapters/stdio.ts
  • src/upstream/cataloger.ts
  • src/upstream/client.ts
  • src/upstream/proxy-middleware.ts
  • tests/adapters.test.ts
  • tests/agent-lease.test.ts
  • tests/cataloger.test.ts
  • tests/config.test.ts
  • tests/daemon-proxy.test.ts
  • tests/daemon-server.test.ts
  • tests/health-tracker.test.ts
  • tests/main-runtime.test.ts
  • tests/proxy-middleware.test.ts
  • tests/run-auth.test.ts
  • tests/run-daemon.test.ts
  • tests/run-management-commands.test.ts
  • tests/run-monitor.test.ts
  • tests/run-proxy.test.ts
  • tests/run-stdio-server.test.ts
  • tests/run-test.test.ts
  • tests/runtime-bootstrap.test.ts
  • tests/runtime-supervisor.test.ts
  • tests/server-capability-surface.test.ts
  • tests/server-capability-tool-executor.test.ts
  • tests/server-handlers.test.ts
  • tests/server-runtime-lifecycle.test.ts
  • tests/server-runtime.test.ts
  • tests/server-session-surface.test.ts
  • tests/server-shell.test.ts
  • tests/status-runner.test.ts
  • tests/upstream-client-oauth.test.ts

| Advisory | Package(s) | Severity | Rationale | Controls | Owner | Review By | Linked Issue |
|---|---|---|---|---|---|---|---|
| _None_ | - | - | - | - | - | - | - |
| `file-type` infinite loop in ASF parser (`>=13.0.0 <21.3.1`) | `@opentui/core -> jimp -> @jimp/core -> file-type` | Moderate | Short-term acceptance is limited to MCP²'s local interactive TUI path. `@opentui/core` is only loaded through `src/tui/config-loader.ts` and `src/tui/monitor-loader.ts`, while the main server/proxy/auth/status/test paths do not import it. The reviewed TUI surfaces exchange config text and monitor JSON state, not arbitrary image or binary payloads, so current project-specific reachability is low. A plain Bun override to `file-type@21.x` cleared `bun audit` but proved runtime-incompatible with `@jimp/core@1.6.0`. | Reassess before the review date and on every `@opentui/core`/`jimp` bump; keep TUI imports lazy; do not route untrusted binary/image content through TUI commands without revisiting this exception; prefer upstream remediation over major-version overrides. | Maintainers | 2026-04-12 | Pending maintainer tracking issue |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Exception review deadline is already expired.

The Review By date is 2026-04-12, which is already in the past as of 2026-06-11. This leaves the accepted vulnerability exception overdue and outside its stated control window; the linked issue should also be a concrete tracking ticket, not “Pending”.

Suggested patch shape
-| ... | Maintainers | 2026-04-12 | Pending maintainer tracking issue |
+| ... | Maintainers | <new-future-review-date> | <tracking-issue-link-or-id> |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/DEPENDENCY_EXCEPTIONS.md` at line 7, The exception entry for the
`file-type` infinite loop is overdue; update the `Review By` date in
DEPENDENCY_EXCEPTIONS.md (the table row referencing `@opentui/core -> jimp ->
`@jimp/core` -> file-type`) to a new future review deadline, replace "Pending
maintainer tracking issue" with a concrete issue/PR reference or tracker ID, and
add a short note in the same table cell stating who is responsible for follow-up
(e.g., maintainer GitHub handle or team) and the action expected (reassess on
`@opentui/core` or `jimp` bumps and before routing untrusted binaries through
TUI).

Comment thread src/cli/main-runtime.ts
Comment on lines +54 to +55
isStderrTty: process.stdout.isTTY,
isStdinTty: process.stdin.isTTY,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix isStderrTty wiring to use stderr.

Line 54 assigns isStderrTty from process.stdout.isTTY; that can route default mode selection incorrectly when stdout/stderr TTY state differs.

Suggested patch
   return {
-    isStderrTty: process.stdout.isTTY,
+    isStderrTty: process.stderr.isTTY,
     isStdinTty: process.stdin.isTTY,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
isStderrTty: process.stdout.isTTY,
isStdinTty: process.stdin.isTTY,
isStderrTty: process.stderr.isTTY,
isStdinTty: process.stdin.isTTY,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/cli/main-runtime.ts` around lines 54 - 55, The isStderrTty property is
incorrectly wired to process.stdout.isTTY; update the assignment so isStderrTty
reads process.stderr.isTTY instead to reflect the actual stderr TTY state
(modify the object/initializer where isStderrTty and isStdinTty are set in
main-runtime.ts).

Comment thread src/cli/run-auth.ts
Comment on lines +251 to +255
if (result.state && !authProvider.verifyState(result.state)) {
console.error("\nError: OAuth state mismatch - possible CSRF attack.");
processRef.exit(1);
return;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Require OAuth state in callback validation.

Line 251 only verifies state when present, so a callback with no state is accepted. That weakens CSRF protection for the auth flow.

Suggested patch
-    if (result.state && !authProvider.verifyState(result.state)) {
+    if (!result.state || !authProvider.verifyState(result.state)) {
       console.error("\nError: OAuth state mismatch - possible CSRF attack.");
       processRef.exit(1);
       return;
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (result.state && !authProvider.verifyState(result.state)) {
console.error("\nError: OAuth state mismatch - possible CSRF attack.");
processRef.exit(1);
return;
}
if (!result.state || !authProvider.verifyState(result.state)) {
console.error("\nError: OAuth state mismatch - possible CSRF attack.");
processRef.exit(1);
return;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/cli/run-auth.ts` around lines 251 - 255, The callback currently only
checks state when result.state is present, allowing missing state to bypass CSRF
protection; update the validation in the callback handler (where result.state
and authProvider.verifyState are used) to treat a missing state as an error: if
result.state is falsy OR authProvider.verifyState(result.state) returns false,
log the OAuth state mismatch and exit (processRef.exit(1)) just as when
verification fails.

Comment thread src/cli/run-daemon.ts
Comment on lines +105 to +107
if (options.socketPath) {
daemonOptions.socketPath = options.socketPath;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use the effective daemon socket path in the instance registry entry.

Line 128 always records monitorSocketPath, even when Lines 105-107 override daemon socketPath. This can publish the wrong endpoint for registry consumers when --socket-path is used.

Suggested fix
-  const monitorSocketPath = getSocketFilePath(configHash);
+  const monitorSocketPath = getSocketFilePath(configHash);
+  const effectiveDaemonSocketPath = options.socketPath ?? monitorSocketPath;
   const runtime = createRuntime({
     config,
     monitorSocketPath,
   });
@@
-  if (options.socketPath) {
-    daemonOptions.socketPath = options.socketPath;
+  if (options.socketPath) {
+    daemonOptions.socketPath = options.socketPath;
   }
@@
       processRef,
       role: "daemon",
-      socketPath: monitorSocketPath,
+      socketPath: effectiveDaemonSocketPath,
       version: VERSION,
     }),
   );

Also applies to: 121-129

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/cli/run-daemon.ts` around lines 105 - 107, The registry entry currently
always uses monitorSocketPath instead of the effective daemon socket path;
update the instance registration to use the final socket path value from
daemonOptions (taking into account options.socketPath override) rather than the
original monitorSocketPath. Locate the code that builds the instance/registry
entry (the block that references monitorSocketPath) and replace that field with
the resolved socket path from daemonOptions.socketPath (or the variable that
holds the effective daemon socket) so consumers get the correct endpoint.

Comment on lines +93 to +113
export function registerShutdownHooks({
includeStdin,
onExit,
processRef = process,
shutdown,
}: RegisterShutdownHooksOptions): void {
processRef.on("SIGINT", () => {
void shutdown(0);
});
processRef.on("SIGTERM", () => {
void shutdown(0);
});

if (includeStdin) {
processRef.stdin.on("close", () => {
void shutdown(0);
});
processRef.stdin.on("end", () => {
void shutdown(0);
});
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard shutdown handling so cleanup executes only once.

Lines 99-112 can trigger shutdown() multiple times (notably stdin end + close), which risks duplicate stop/unregister paths and racey teardown behavior.

Suggested fix
 export function registerShutdownHooks({
   includeStdin,
   onExit,
   processRef = process,
   shutdown,
 }: RegisterShutdownHooksOptions): void {
-  processRef.on("SIGINT", () => {
-    void shutdown(0);
-  });
-  processRef.on("SIGTERM", () => {
-    void shutdown(0);
-  });
+  let shuttingDown = false;
+  const triggerShutdown = (exitCode: number): void => {
+    if (shuttingDown) return;
+    shuttingDown = true;
+    void shutdown(exitCode);
+  };
+
+  processRef.on("SIGINT", () => {
+    triggerShutdown(0);
+  });
+  processRef.on("SIGTERM", () => {
+    triggerShutdown(0);
+  });
 
   if (includeStdin) {
     processRef.stdin.on("close", () => {
-      void shutdown(0);
+      triggerShutdown(0);
     });
     processRef.stdin.on("end", () => {
-      void shutdown(0);
+      triggerShutdown(0);
     });
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function registerShutdownHooks({
includeStdin,
onExit,
processRef = process,
shutdown,
}: RegisterShutdownHooksOptions): void {
processRef.on("SIGINT", () => {
void shutdown(0);
});
processRef.on("SIGTERM", () => {
void shutdown(0);
});
if (includeStdin) {
processRef.stdin.on("close", () => {
void shutdown(0);
});
processRef.stdin.on("end", () => {
void shutdown(0);
});
}
export function registerShutdownHooks({
includeStdin,
onExit,
processRef = process,
shutdown,
}: RegisterShutdownHooksOptions): void {
let shuttingDown = false;
const triggerShutdown = (exitCode: number): void => {
if (shuttingDown) return;
shuttingDown = true;
void shutdown(exitCode);
};
processRef.on("SIGINT", () => {
triggerShutdown(0);
});
processRef.on("SIGTERM", () => {
triggerShutdown(0);
});
if (includeStdin) {
processRef.stdin.on("close", () => {
triggerShutdown(0);
});
processRef.stdin.on("end", () => {
triggerShutdown(0);
});
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/cli/runtime-bootstrap.ts` around lines 93 - 113, The shutdown hook can be
invoked multiple times (SIGINT/SIGTERM and stdin 'end'/'close'), so wrap calls
to shutdown in a one-time guard: inside registerShutdownHooks create a local
boolean (e.g., "shutdownCalled") and a small wrapper function (e.g.,
"invokeShutdown(code)") that returns immediately if shutdownCalled is true, sets
it true on first call, then calls void shutdown(code); replace all direct
shutdown(...) calls (the signal handlers and stdin handlers) with this wrapper
so cleanup runs only once; alternatively use processRef.once for signals but
still protect stdin handlers with the same one-time guard to avoid duplicate
teardown.

Comment thread src/upstream/client.ts
Comment on lines 345 to 358
// Retry connection after auth
log("Retrying connection after OAuth...");
if (sseConfig && httpTransportFactory) {
await safelyCloseTransport(transport);
httpTransport = httpTransportFactory(
sseConfig,
log,
verbose,
authProvider,
);
transport = httpTransport as Transport;
}
// biome-ignore lint/style/noNonNullAssertion: transport is assigned above for all code paths
await Promise.race([client.connect(transport!), timeoutPromise]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Start a fresh connect timeout for the post-OAuth retry.

The retry still races client.connect() against the original timeoutPromise, which started before the browser flow. If authorization takes longer than timeoutMs, the recreated transport will fail immediately on retry even though auth succeeded. Build a new timeout window for each connect attempt.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/upstream/client.ts` around lines 345 - 358, The retry is still racing
client.connect(transport) against the original timeoutPromise created before the
OAuth flow, so recreate a fresh timeout promise for the post-OAuth connect
attempt; before awaiting Promise.race([client.connect(transport!),
timeoutPromise]) in the retry block, build a new timeoutPromise (using the same
timeoutMs/timeout creation logic used originally) so that each connect() call
races against a newly-started timeout; locate this in the retry branch around
safelyCloseTransport/ httpTransportFactory/transport and replace the reused
timeoutPromise with the newly created one.

Comment on lines +130 to +145
const sessionMap = new Map<string, string>();

return {
name: "session-affinity",
fn: (ctx) => {
const sessionId = ctx.callContext?.sessionId;
if (!sessionId) return;

// Track session -> upstream mapping
const existing = sessionMap.get(sessionId);
if (existing && existing !== ctx.upstreamKey) {
// Session was previously on a different upstream
ctx.state["sessionAffinityConflict"] = true;
ctx.state["previousUpstream"] = existing;
}
sessionMap.set(sessionId, ctx.upstreamKey);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

sessionMap grows without any eviction path.

Every new sessionId is retained forever, and this middleware has no cleanup hook to remove dead sessions. In a long-lived proxy process, that becomes an unbounded in-memory cache. Add TTL/LRU eviction or a teardown path tied to session end.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/upstream/proxy-middleware.ts` around lines 130 - 145, The sessionMap used
by the "session-affinity" middleware (symbol: sessionMap, middleware name
"session-affinity", handler fn) never evicts entries causing unbounded memory
growth; implement eviction by either replacing sessionMap with an LRU/TTL cache
(use an LRU cache library or a Map keyed to sessionId with stored timestamps and
a max size/ttl) and run periodic cleanup, or add a teardown hook tied to session
lifecycle (e.g., remove sessionMap entry when ctx.callContext signals session
end) so entries are removed when sessions terminate; ensure lookups still use
sessionMap.get(sessionId) and updates use sessionMap.set(sessionId,
ctx.upstreamKey).

Comment on lines +204 to +214
fn: (ctx) => {
const retryCount = (ctx.state["retryCount"] as number) ?? 0;
if (ctx.response?.error && retryCount < maxRetries) {
ctx.state["retryCount"] = retryCount + 1;
ctx.state["retryDelayMs"] = baseDelayMs * 2 ** retryCount;
ctx.state["shouldRetry"] = true;
}
},
options: {
phases: ["after_call"],
priority: 150,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Retry state is skipped for the dedicated error phase.

The retry middleware only subscribes to after_call, so callers that surface transport failures through on_error never set shouldRetry/retryDelayMs. Register the middleware for the error phase as well, or move retry detection there so thrown failures participate in backoff.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/upstream/proxy-middleware.ts` around lines 204 - 214, The retry detection
currently in the middleware fn (checking ctx.response?.error, retryCount,
maxRetries and setting ctx.state["shouldRetry"]/["retryDelayMs"]) only runs in
the "after_call" phase per options.phases; register this middleware for the
error phase as well or move the retry-detection logic into the on_error handler
so transport failures set ctx.state["shouldRetry"] and
ctx.state["retryDelayMs"]. Update the options.phases to include "on_error" (or
relocate the fn's logic to the on_error hook) so functions using on_error
participate in backoff, keeping the same checks for retryCount, maxRetries,
baseDelayMs and state keys.

Comment on lines +403 to +411
await firstDaemon.stop();
await secondDaemon.start();
await new Promise((resolve) => setTimeout(resolve, 250));

const recoveredTools = (await client.listTools()).tools.map(
(tool) => tool.name,
);
expect(recoveredTools).toContain("time_util");
} finally {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Replace fixed sleep with condition-based recovery wait.

Line 405 uses a hardcoded 250ms delay before asserting reconnection. This is timing-sensitive and can flake under slower CI runs; use waitFor on the recovered tool condition instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/daemon-proxy.test.ts` around lines 403 - 411, Replace the fixed 250ms
sleep with a condition-based wait that polls until the reconnection is observed:
after calling await secondDaemon.start(), loop or use a waitFor helper to
repeatedly call await client.listTools() (or reuse recoveredTools logic) and
check that the returned tool names include "time_util", with a sensible timeout
(e.g., few seconds) and short interval between attempts; update the block around
firstDaemon, secondDaemon, and client.listTools() so the test only proceeds when
the recovered tool condition is met instead of awaiting a hardcoded setTimeout.

Comment thread tests/daemon-proxy.test.ts Outdated
…r election

The test was asserting that bridge-1 is the owner after daemon replacement,
but owner election during reconnection is non-deterministic since the
replacement daemon doesn't retain the previous owner identity. Updated
assertion to verify the owner is one of the 3 expected bridges.

Co-authored-by: MiMoCode Agent <mimo@xiaomi.com>
@codecov

codecov Bot commented Jun 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 83.03748% with 516 lines in your changes missing coverage. Please review.
✅ Project coverage is 76.63%. Comparing base (41144bf) to head (3aadb1b).

Files with missing lines Patch % Lines
src/cli/run-monitor.ts 38.07% 122 Missing ⚠️
src/cli/run-auth.ts 63.24% 68 Missing ⚠️
src/cli/main-runtime.ts 39.32% 54 Missing ⚠️
src/cli/run-test.ts 76.87% 37 Missing ⚠️
src/cli/tui-runtime.ts 5.40% 35 Missing ⚠️
src/daemon/proxy.ts 83.09% 35 Missing ⚠️
src/runtime/supervisor.ts 85.77% 34 Missing ⚠️
src/server/runtime-lifecycle.ts 74.79% 31 Missing ⚠️
src/cli/run-stdio-server.ts 78.89% 23 Missing ⚠️
src/upstream/adapters/stdio.ts 56.81% 19 Missing ⚠️
... and 10 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #25      +/-   ##
==========================================
+ Coverage   69.84%   76.63%   +6.79%     
==========================================
  Files         112      140      +28     
  Lines       12520    14018    +1498     
==========================================
+ Hits         8744    10743    +1999     
+ Misses       3776     3275     -501     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@aditzel
aditzel merged commit 399c94b into main Jun 12, 2026
11 checks passed
@aditzel
aditzel deleted the codex/0.9-runtime-identity branch June 12, 2026 15:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant