Skip to content

feat: multi-provider orchestration with Elixir OTP harness - #1

Merged
ranvier2d2 merged 8 commits into
mainfrom
feat/opencode-subagents
Mar 24, 2026
Merged

feat: multi-provider orchestration with Elixir OTP harness#1
ranvier2d2 merged 8 commits into
mainfrom
feat/opencode-subagents

Conversation

@ranvier2d2

@ranvier2d2 ranvier2d2 commented Mar 24, 2026

Copy link
Copy Markdown
Collaborator

Summary

Multi-provider AI coding orchestration for T3 Code — 4 providers (Claude, Codex, Cursor, OpenCode) running through a hybrid Node SDK + Elixir OTP architecture.

  • Hybrid provider routing: Claude + Codex always via Node SDK (Agent SDK / CLI), Cursor + OpenCode via Elixir harness when available
  • Elixir OTP harness: DynamicSupervisor manages provider sessions as GenServers with crash isolation, WAL-based event replay, and Phoenix Channel transport
  • Playwright E2E tests: All 4 providers verified end-to-end against the live app with screenshot evidence
  • 25+ CodeRabbit findings resolved: Buffer corruption in Erlang Ports, spawn failure handling, session validation, Codex delta deduplication, and more

Architecture

UI → WebSocket → Node Server → Provider Adapter Registry
                                 ├── Claude  → Node SDK (@anthropic-ai/claude-agent-sdk)
                                 ├── Codex   → Node SDK (codex app-server)
                                 ├── Cursor  → Elixir Harness → cursor-agent CLI
                                 └── OpenCode → Elixir Harness → opencode CLI

Key Changes

Provider Routing (serverLayers.ts)

  • Single makeServerProviderLayer() — no conditional modes
  • Claude + Codex: always Node SDK adapters
  • Cursor + OpenCode: Elixir harness when harnessPort configured, otherwise ProviderUnsupportedError

Elixir Harness (apps/harness/)

  • SessionManager routes to provider-specific GenServers (ClaudeSession, CursorSession, OpenCodeSession, CodexSession)
  • Stream-JSON parsing via Erlang Ports with proper :noeol/:eol buffer handling
  • WAL ring buffer (500 events) for reconnection replay
  • Phoenix Channel for bidirectional event transport

E2E Tests (apps/web/e2e/)

  • @playwright/test with page-object helpers
  • Parameterized across all 4 providers
  • Screenshots at each step: provider selection, message sent, response received

Bug Fixes (CodeRabbit)

  • CRITICAL: :eol handler in cursor_session.ex and claude_session.ex now prepends buffered :noeol data before JSON decode
  • Codex stuttering: suppressed duplicate agent_message_content_delta event
  • cursor_session.ex: spawn failure returns error (not {:ok}), synthetic IDs not passed to --resume, read_thread returns actual turns
  • session_manager.ex: validates threadId, emits session/connecting only for new sessions
  • projector.ex: clears active_turn on session/error
  • HarnessClientManager: detects "Session not found" errors, handles WAL replay gaps
  • ProviderHealth: consistent cursor-agent binary usage

Test Plan

  • vitest run — unit tests pass (appSettings 21/21, codexCliVersion 26/26)
  • playwright test — E2E 8/8 (Codex, Claude, Cursor, OpenCode × send+receive, picker)
  • tsc --noEmit — compiles clean (1 pre-existing error in ProviderRuntimeIngestion.ts)
  • CodeRabbit: 29 → 4 findings (remaining are style/noise)

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Added end-to-end test coverage for provider functionality with Playwright
    • Extended harness event handling for subagent/task lifecycle operations
    • Added Claude binary path configuration option for custom installations
    • Introduced benchmark infrastructure for performance testing and analysis
  • Bug Fixes

    • Improved error handling for invalid/missing session parameters
    • Enhanced session provider detection and resolution
    • Added explicit error returns when port connections unavailable
    • Improved error logging and messaging for troubleshooting
  • Tests

    • Expanded test coverage for projector, snapshot, and adapter functionality
    • Added comprehensive benchmark suite for performance metrics
    • Introduced provider integration test matrix
  • Documentation

    • Added architectural guidance on runtime choice (Elixir vs Node)
    • Created test plan documenting coverage priorities

@coderabbitai

coderabbitai Bot commented Mar 24, 2026

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4c46db5b-55c5-49ae-bcdb-2dfe5a963aa4

📥 Commits

Reviewing files that changed from the base of the PR and between b8117b3a639207965cede20a15f926d008955c85 and e72e0f2.

⛔ Files ignored due to path filters (21)
  • bun.lock is excluded by !**/*.lock
  • docs/diagrams/provider-protocol-convergence.svg is excluded by !**/*.svg
  • docs/images/crossover-event-loop-lag.png is excluded by !**/*.png
  • docs/images/crossover-failure-storm.png is excluded by !**/*.png
  • docs/images/crossover-payload-ramp.png is excluded by !**/*.png
  • docs/images/crossover-session-ramp.png is excluded by !**/*.png
  • docs/images/crossover-subagent-ramp.png is excluded by !**/*.png
  • docs/images/crossover-summary.png is excluded by !**/*.png
  • docs/images/crossover-sustained-leak.png is excluded by !**/*.png
  • docs/images/event-loop-lag.png is excluded by !**/*.png
  • docs/images/event-timeline-waterfall.png is excluded by !**/*.png
  • docs/images/gc-lab-attribution.png is excluded by !**/*.png
  • docs/images/loc-per-provider.png is excluded by !**/*.png
  • docs/images/memory-leak-divergence.png is excluded by !**/*.png
  • docs/images/multi-provider-comparison.png is excluded by !**/*.png
  • docs/images/real-subagent-lifecycle.png is excluded by !**/*.png
  • docs/images/real-subagent-memory.png is excluded by !**/*.png
  • docs/images/real-workload-stability.png is excluded by !**/*.png
  • docs/images/scale50-distribution.png is excluded by !**/*.png
  • docs/images/scorecard.png is excluded by !**/*.png
  • pixi.lock is excluded by !**/*.lock
📒 Files selected for processing (89)
  • .github/workflows/ci.yml
  • README.md
  • TEST_PLAN.md
  • apps/harness/AGENTS.md
  • apps/harness/bin/harness
  • apps/harness/config/runtime.exs
  • apps/harness/lib/harness/projector.ex
  • apps/harness/lib/harness/providers/claude_session.ex
  • apps/harness/lib/harness/providers/codex_session.ex
  • apps/harness/lib/harness/providers/cursor_session.ex
  • apps/harness/lib/harness/providers/mock_session.ex
  • apps/harness/lib/harness/providers/opencode_session.ex
  • apps/harness/lib/harness/session_manager.ex
  • apps/harness/lib/harness/snapshot.ex
  • apps/harness/lib/harness/snapshot_server.ex
  • apps/harness/lib/harness_web/harness_channel.ex
  • apps/harness/test/harness/projector_test.exs
  • apps/harness/test/harness/snapshot_server_test.exs
  • apps/harness/test/live_e2e_test.exs
  • apps/server/src/attachmentPaths.test.ts
  • apps/server/src/main.ts
  • apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts
  • apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts
  • apps/server/src/orchestration/Layers/ProviderCommandReactor.ts
  • apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts
  • apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts
  • apps/server/src/provider/Layers/ClaudeAdapter.test.ts
  • apps/server/src/provider/Layers/ClaudeAdapter.ts
  • apps/server/src/provider/Layers/CodexAdapter.ts
  • apps/server/src/provider/Layers/HarnessClientAdapter.ts
  • apps/server/src/provider/Layers/HarnessClientManager.ts
  • apps/server/src/provider/Layers/ProviderAdapterRegistry.ts
  • apps/server/src/provider/Layers/ProviderHealth.ts
  • apps/server/src/provider/Layers/codexEventMapping.ts
  • apps/server/src/provider/codexCliVersion.test.ts
  • apps/server/src/provider/codexCliVersion.ts
  • apps/server/src/serverLayers.ts
  • apps/server/src/terminal/Layers/BunPTY.ts
  • apps/server/src/terminal/Layers/NodePTY.ts
  • apps/web/e2e/helpers.ts
  • apps/web/e2e/providers.spec.ts
  • apps/web/package.json
  • apps/web/playwright.config.ts
  • apps/web/src/appSettings.test.ts
  • apps/web/src/appSettings.ts
  • apps/web/src/components/ChatView.tsx
  • apps/web/src/components/chat/ProviderModelPicker.tsx
  • apps/web/src/composerDraftStore.ts
  • apps/web/src/routes/_chat.settings.tsx
  • docs/.nojekyll
  • docs/diagrams/agent-framework-isolation-gap.html
  • docs/diagrams/let-it-crash-vs-defensive-coding.html
  • docs/elixir-should-not-replace-node.md
  • docs/index.html
  • docs/post.md
  • docs/test-plan.md
  • packages/contracts/src/model.ts
  • packages/shared/src/model.ts
  • scripts/benchmark-analyze.ts
  • scripts/benchmark-failure-storm.ts
  • scripts/benchmark-payload-ramp.ts
  • scripts/benchmark-runner-cli.test.ts
  • scripts/benchmark-runner-cli.ts
  • scripts/benchmark-runner.ts
  • scripts/benchmark-session-ramp.ts
  • scripts/benchmark-subagent-ramp.ts
  • scripts/benchmark-sustained-leak.ts
  • scripts/crash-isolation-proof.ts
  • scripts/debug-provider-events.ts
  • scripts/dev-runner.ts
  • scripts/harness-dry-run.ts
  • scripts/mock-codex-server.ts
  • scripts/mock-provider.sh
  • scripts/node-metrics-collector.ts
  • scripts/stress-test-analyze.ts
  • scripts/stress-test-concurrent-elixir.ts
  • scripts/stress-test-exception.ts
  • scripts/stress-test-gc-lab-elixir.ts
  • scripts/stress-test-memory-leak.ts
  • scripts/stress-test-node.ts
  • scripts/stress-test-real-claude.ts
  • scripts/stress-test-real-subagent.ts
  • scripts/stress-test-real-workload.ts
  • scripts/stress-test-runner.ts
  • scripts/stress-test-scale50.ts
  • scripts/stress-test-subagent.ts
  • scripts/test-harness-connection.ts
  • scripts/test-harness-mapping.ts
  • scripts/test-harness-prompt.ts

📝 Walkthrough

Walkthrough

Wide-ranging changes across Harness (Elixir) and Server (TypeScript) touch provider/session lifecycles, PTY runtime selection, Claude SDK options, error handling in stress/benchmark scripts, new e2e Playwright tests, many benchmark scripts, and assorted test/spec additions and formatting refinements.

Changes

Cohort / File(s) Summary
Harness build & config
apps/harness/bin/harness, apps/harness/config/runtime.exs
Combined cd && mix compile into an if conditional; added production validation trimming/enforcing T3CODE_HARNESS_SECRET (reject default/empty).
Harness provider sessions
apps/harness/lib/harness/providers/codex_session.ex, apps/harness/lib/harness/providers/claude_session.ex, apps/harness/lib/harness/providers/cursor_session.ex, apps/harness/lib/harness/providers/mock_session.ex, apps/harness/lib/harness/providers/opencode_session.ex
Added guard for sending when port is nil; buffer concat for CLAUDE line fragments; cursor resume/has_real_chat_id changes; safer pending-reply handling; extensive OpenCode SSE→harness event mappings for subtask/agent/step events and session.created parent-child linking.
Harness projection & snapshot
apps/harness/lib/harness/projector.ex, apps/harness/lib/harness/snapshot.ex, apps/harness/lib/harness/snapshot_server.ex, apps/harness/lib/harness/snapshot_server.ex
Clear active_turn on session/error; allow runtime_mode to be nil; adjust replay gap boundary (after_seq < oldest_seq - 1).
Harness channel / session manager / tests
apps/harness/lib/harness/session_manager.ex, apps/harness/lib/harness/snapshot_server.ex, apps/harness/test/*, apps/harness/test/harness/projector_test.exs, apps/harness/test/live_e2e_test.exs
start_session/1 now returns error for missing threadId and resolves actual provider on already-started via Registry lookup; tests updated to use state inspection over sleeps and many new projector tests for approval/request handling and runtime modes.
Server provider adapters & mapping
apps/server/src/provider/Layers/HarnessClientAdapter.ts, apps/server/src/provider/Layers/HarnessClientManager.ts, apps/server/src/provider/Layers/ClaudeAdapter.ts, apps/server/src/provider/Layers/codexEventMapping.ts, apps/server/src/provider/Layers/ProviderHealth.ts
Added Claude settingSources to SDK options; expanded harness→runtime event mappings (agent spawn, task progress/notification, waiting lifecycle); unified replay push path and stronger replay error handling; codex event delta mapping quieted/removed; Claude binary resolution added with FileSystem lookup and changed check type.
Server runtime/PTY refactor
apps/server/src/serverLayers.ts, apps/server/src/terminal/Layers/BunPTY.ts, apps/server/src/terminal/Layers/NodePTY.ts, apps/server/src/main.ts
Switched PTY adapters to runtime dynamic imports (loader) and renamed exported layers to layer; adjusted provider registry construction and removed conditional harness-provider import in main.
Attachment & ingestion changes
apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts, apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts, apps/server/src/attachmentPaths.test.ts
After auto-create of missing thread, add 50ms sleep before re-read and log/drop if still missing; test prefix rename; added attachment path normalization/resolution tests.
Web app settings & e2e
apps/web/src/appSettings.ts, apps/web/src/appSettings.test.ts, apps/web/src/components/ChatView.tsx, apps/web/src/routes/_chat.settings.tsx, apps/web/e2e/*, apps/web/playwright.config.ts, apps/web/package.json
Added claudeBinaryPath setting and UI input; relaxed custom-model map typing; ChatView includes Claude in provider options and depends on claudeBinaryPath; added Playwright helpers, providers e2e tests and config, and test scripts/dev deps.
Scripts: stress/benchmark/test utilities
scripts/*.ts, scripts/*.sh, scripts/*benchmark*.ts (many files)
Wide changes: improved error handling for dynamic Harness imports (warn + guard), stricter argument validation, JSON-RPC error → Promise rejection, added numerous new benchmark scripts and CLI helpers, formatting and type-tightening across many runner/stress scripts.
Docs & misc
README.md, TEST_PLAN.md, docs/*, apps/harness/AGENTS.md, packages/*, apps/server/src/provider/* tests
Added TEST_PLAN, many docs/diagrams/posts, README table formatting, agent docs tweaks, new/updated tests (codexCliVersion, provider tests), minor formatting/type tweaks across packages.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

"A twitchy rabbit in a server room hops,
compiling Elixir under midnight lamps,
I guard secrets, spawn agents, and watch PTYs swap,
with tests and benchmarks in my tiny paws —
hop, review, repeat — the code blooms like clover." 🐇✨

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/opencode-subagents

@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: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/harness/config/runtime.exs`:
- Around line 15-17: The current check only rejects the literal
"dev-harness-secret" so blank or whitespace values still pass; update the
conditional around harness_secret to reject nil/empty/whitespace and the default
literal by normalizing (e.g., trim and fallback to empty string) and then
raising when the trimmed value is empty or equals "dev-harness-secret" (keep the
same raise message), ensuring this validation runs where the existing if
harness_secret ... end block and the harness_secret variable are defined.

In `@apps/harness/lib/harness/providers/codex_session.ex`:
- Around line 540-542: send_to_port/2 currently returns {:error, :port_closed}
but callers ignore its result, causing false-success replies; change
send_to_port/2 callsites in this module to propagate and handle its {:ok, _} |
{:error, _} return instead of discarding it (e.g., replace blind
send_to_port(...) calls with a case or with {:ok, _} = send_to_port(...) pattern
or by returning the error upstream), and update any higher-level functions that
aggregate those calls so they return an error when transport fails rather than
always returning :ok; keep send_to_port/2 signature but ensure its callers (all
places invoking send_to_port/2 in this file) do not ignore the result and
properly propagate {:error, :port_closed}.

In `@apps/harness/lib/harness/session_manager.ex`:
- Around line 89-95: The lifecycle event emission uses the incoming provider
before reuse resolution, causing mismatched provider state; update the flow so
the code resolves the running provider via
Registry.lookup(Harness.SessionRegistry, thread_id) into actual_provider
(currently computed in the reuse branch) before emitting the
"session/connecting" event, and use actual_provider in the event payload (or
move/hoist the "session/connecting" emission to after the start/reuse decision
is made) so the emitted provider always matches the provider returned for the
session.

In `@apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts`:
- Around line 890-891: Replace the single fixed sleep used after auto-creating a
thread in ProviderRuntimeIngestion (the yield* Effect.sleep("50 millis") after
the thread.create handling) with a bounded readiness retry loop: attempt up to
~8 retries with a small backoff (e.g., 25ms) between attempts and re-check the
thread materialization/lookup each iteration, proceeding once materialized or
falling back only after retries exhausted; apply the same retry pattern to the
later branch where the code currently logs "Dropping event" when the thread is
still missing so that the drop path only occurs after the bounded retry attempts
fail. Ensure you modify the thread-create handling and the subsequent
missing-thread check (look for references to thread.create, the sleep call, and
the "Dropping event" log) to use the retry-with-backoff logic instead of a
single sleep.

In `@apps/server/src/serverLayers.ts`:
- Around line 53-57: The current runtime selection sets runtime = "bun" whenever
Bun is present, which causes Bun on Windows to import BunPTY (whose layer dies);
change the selection logic so that when process.versions.bun is present but
process.platform === "win32" you fall back to the Node adapter. Update the
runtime/loading branch that uses runtime and runtimePtyAdapterLoaders (the
runtime variable and loader lookup used to produce ptyAdapterModule in
makeServerRuntimeServicesLayer) to choose 'node' instead of 'bun' on Windows so
Bun installs use the Node PTY adapter.

In `@scripts/stress-test-node.ts`:
- Around line 496-501: The turnTimings entry for a given tid can remain stale
because only the timeout path deletes it; update measureTurnLatency so every
completion path removes turnTimings.get(tid): modify the resolver stored in
turnTimings (resolver: (lat) => { turnTimings.delete(tid); clearTimeout(timer);
resolve(lat); }) and also delete the entry in the sendTurn rejection handler
(sendTurn(session, prompt).catch(() => { turnTimings.delete(tid);
clearTimeout(timer); resolve(-1); })), keeping the existing timeout path that
already deletes the entry.

In `@scripts/test-harness-connection.ts`:
- Around line 46-55: After JSON.parse, validate that the parsed msg is an array
of expected length and types before destructuring: check Array.isArray(msg) and
msg.length >= 5, then safely extract elements (or index into msg) and normalize
the ref to a string (e.g., const refStr = String(ref)) before using
pending.has(refStr); also ensure event is a string before comparing to
"phx_reply" so destructuring and lookups cannot throw on malformed frames in the
block around msg, msg parsing, and the event/ref handling.
🪄 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: 3af1b4e3-e44d-43cd-8865-0542a75899ef

📥 Commits

Reviewing files that changed from the base of the PR and between 929a3ce and b8117b3a639207965cede20a15f926d008955c85.

📒 Files selected for processing (24)
  • apps/harness/bin/harness
  • apps/harness/config/runtime.exs
  • apps/harness/lib/harness/providers/codex_session.ex
  • apps/harness/lib/harness/providers/opencode_session.ex
  • apps/harness/lib/harness/session_manager.ex
  • apps/harness/lib/harness/snapshot.ex
  • apps/harness/test/harness/snapshot_server_test.exs
  • apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts
  • apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts
  • apps/server/src/provider/Layers/ClaudeAdapter.test.ts
  • apps/server/src/provider/Layers/ClaudeAdapter.ts
  • apps/server/src/provider/Layers/HarnessClientAdapter.ts
  • apps/server/src/provider/Layers/HarnessClientManager.ts
  • apps/server/src/serverLayers.ts
  • apps/server/src/terminal/Layers/BunPTY.ts
  • apps/server/src/terminal/Layers/NodePTY.ts
  • scripts/node-metrics-collector.ts
  • scripts/stress-test-exception.ts
  • scripts/stress-test-node.ts
  • scripts/stress-test-real-claude.ts
  • scripts/stress-test-real-workload.ts
  • scripts/stress-test-scale50.ts
  • scripts/stress-test-subagent.ts
  • scripts/test-harness-connection.ts

Comment thread apps/harness/config/runtime.exs Outdated
Comment thread apps/harness/lib/harness/providers/codex_session.ex
Comment thread apps/harness/lib/harness/session_manager.ex
Comment thread apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts
Comment thread apps/server/src/serverLayers.ts Outdated
Comment thread scripts/stress-test-node.ts
Comment on lines +46 to 55
let msg: unknown[];
try {
msg = JSON.parse(text);
} catch (err) {
console.error(" Failed to parse message:", err);
return;
}
const [, ref, , event, payload] = msg;

if (event === "phx_reply" && ref && pending.has(ref)) {

@coderabbitai coderabbitai Bot Mar 24, 2026

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 | 🟡 Minor

Validate Phoenix frame shape before destructuring.

At Line 53, parsing success does not guarantee tuple shape. A non-array payload can still throw at destructuring, and ref should be normalized to string for pending lookups.

Suggested fix
-    let msg: unknown[];
+    let msg: unknown;
     try {
       msg = JSON.parse(text);
     } catch (err) {
       console.error("  Failed to parse message:", err);
       return;
     }
-    const [, ref, , event, payload] = msg;
+    if (!Array.isArray(msg) || msg.length !== 5) {
+      console.warn("  Ignoring non-Phoenix frame");
+      return;
+    }
+    const [, refRaw, , event, payload] = msg;
+    const ref = refRaw == null ? null : String(refRaw);
 
-    if (event === "phx_reply" && ref && pending.has(ref)) {
+    if (event === "phx_reply" && ref !== null && pending.has(ref)) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/test-harness-connection.ts` around lines 46 - 55, After JSON.parse,
validate that the parsed msg is an array of expected length and types before
destructuring: check Array.isArray(msg) and msg.length >= 5, then safely extract
elements (or index into msg) and normalize the ref to a string (e.g., const
refStr = String(ref)) before using pending.has(refStr); also ensure event is a
string before comparing to "phx_reply" so destructuring and lookups cannot throw
on malformed frames in the block around msg, msg parsing, and the event/ref
handling.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Acceptable for a dev diagnostic script. The Phoenix Channel protocol guarantees well-formed JSON frames over WebSocket, so parse failures here indicate a broken transport (which surfaces as a connection error). Adding defensive validation would obscure the real issue in a tool meant for debugging.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@ranvier2d2, that's a fair point — for a diagnostic tool, noisy failure is preferable to silent swallowing, and the transport error will surface clearly before any malformed frame could appear. I'll defer to your judgment here.

🐇 🤔


✏️ Learnings added
Learnt from: ranvier2d2
Repo: Ranvier-Technologies/t3code-OTP PR: 1
File: scripts/test-harness-connection.ts:46-55
Timestamp: 2026-03-24T15:20:48.633Z
Learning: In `scripts/test-harness-connection.ts`, the lack of defensive Array shape validation after `JSON.parse` is intentional. This is a developer diagnostic script where Phoenix Channel protocol guarantees well-formed frames; parse failures indicate broken transport and should surface loudly rather than be silently caught. Adding frame-shape guards would obscure the real issue in a debugging tool.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@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: b8117b3a63

ℹ️ 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 scripts/stress-test-real-claude.ts Outdated
Comment on lines +178 to +180
const child = spawn("claude", [
"--print",
"--output-format", "stream-json",

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 Close Claude stdin when launching --print workload

Switching from the shell command to spawn("claude", ...) removed the previous < /dev/null redirection, so each child now keeps an open stdin pipe. In environments where claude --print still reads stdin when available, these runs can hang waiting for EOF, which skews stress-test completion and latency metrics. Please explicitly close stdin (for example stdio[0] = "ignore" or child.stdin.end()) to preserve the prior non-interactive behavior.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

✅ Fixed. Changed stdio[0] from "pipe" to "ignore" so stdin is closed immediately, matching the prior < /dev/null behavior.

Comment on lines +821 to +823
step_id = Map.get(part, "id")

if step_id do

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 Complete OpenCode steps even when finish payload omits ID

step-start emits item/started with a synthetic ID when part.id is missing, but step-finish only emits completion if part.id is present. When OpenCode omits IDs (which the start path already anticipates), the completion event is dropped and the UI can show permanently-running step items. Persisting/deriving the fallback ID for finish events would avoid dangling work-log entries.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

✅ Fixed. step-finish now generates a fallback ID with generate_id() when part.id is missing (same pattern as step-start). The nil guard was removed so completion events are always emitted, preventing dangling work-log entries.

ranvier2d2 and others added 8 commits March 24, 2026 12:03
Clear the listSessions cache when startSession or stopSession is
called, preventing stale cache from causing "Cannot recover thread"
errors when ProviderService.sendTurn races with session creation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cherry-picked from pingdotgg/t3code@754cded2.
Changes PTY adapter selection from static typeof-Bun check to
runtime dynamic import. Renames exports to `layer` convention.

Original authors: shivam, Julius Marminge
Cherry-picked from pingdotgg/t3code@2b6640af.
Adds settingSources: ["user", "project", "local"] to Claude SDK
createQuery so it loads CLAUDE.md, project settings, and local
config from the filesystem.

Original author: Harshit Agarwal
Classify subtask/agent/step-start/step-finish SSE part types in
opencode_session.ex, track child sessions via parentID, and map
collab_agent_spawn_begin to task.started in HarnessClientAdapter.

Verified E2E: OpenCode dispatched 2 parallel subagents (Explore +
General), child sessions tracked, steps and subtask tool calls
visible in UI work log.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Change collab_agent_spawn_begin mapping from task.started (filtered
by session-logic.ts) to item.started with collab_agent_tool_call
itemType. Child session spawns now appear in the UI work log with
HammerIcon alongside step entries.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
HIGH: fix pushChannel→push runtime crash in HarnessClientManager,
add warning log + 50ms delay for projection lag in ProviderRuntimeIngestion,
fix provider mismatch in SessionManager already_started, guard nil port
in CodexSession, fix runtime_mode typespec in Snapshot.

MEDIUM: enforce T3CODE_HARNESS_SECRET in prod, replace Process.sleep
with :sys.get_state in snapshot tests.

LOW: add import error logging to 5 stress-test scripts, validate
duration in node-metrics-collector, fix dead code in bin/harness,
wrap JSON.parse in test-harness-connection, fix shell injection in
stress-test-real-claude spawn, fix async executor anti-pattern and
empty catch in stress-test-node, deduplicate test prefix in
ProjectionPipeline.test.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Claude --print mode emits task_progress and task_notification as
system messages through the Elixir harness. These were falling
through to codexEventMapping which doesn't handle unprefixed task
events, causing them to be logged as "unmapped" and dropped.

Now handled in mapHarnessEventToRuntimeEvents:
- task_progress → item.started with tool name and description
- task_notification → item.completed with summary
- collab_waiting_begin → item.started (parent waits for subagent)
- collab_waiting_end → item.completed (subagent finished)

Verified via 4-provider E2E test: Claude spawned 2 subagents
(task_ids a17d50dd... and a6bbcc70...) that were previously
invisible in the UI.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Provider routing:
- Single makeServerProviderLayer: Claude + Codex via Node SDK,
  Cursor + OpenCode via Elixir harness (conditional on harnessPort)
- claudeBinaryPath app setting + health check fallback paths
- Codex delta dedup: suppress duplicate agent_message_content_delta

E2E tests:
- @playwright/test setup at apps/web/e2e/ with page-object helpers
- All 4 providers verified (send+receive, picker) with screenshot evidence

CodeRabbit fixes:
- cursor_session.ex: :noeol buffer, spawn failure reply, synthetic resume ID,
  read_thread returns actual turns
- claude_session.ex: :noeol buffer prepend
- session_manager.ex: validate threadId, connecting event after start_child
- projector.ex: clear active_turn on session/error
- snapshot_server.ex: replay gap off-by-one
- HarnessClientAdapter.ts: session-not-found pattern, replay gap handling
- ProviderHealth.ts: cursor-agent binary, Claude fallback paths
- codexEventMapping.ts: remove duplicate delta handler
- composerDraftStore.ts: inferProviderForModel for stickyModel
- appSettings.ts: Partial<Record> for custom model config
- opencode_session.ex: step-finish fallback ID
- runtime.exs: reject blank/whitespace harness secret
- serverLayers.ts: Bun on Windows PTY fallback
- CI vouch gate: only run on vouch:trusted PRs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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