Skip to content

fix: desktop harness bundling + council top-5 harness fixes - #26

Merged
ranvier2d2 merged 5 commits into
mainfrom
fix/council-top5-harness
Mar 28, 2026
Merged

fix: desktop harness bundling + council top-5 harness fixes#26
ranvier2d2 merged 5 commits into
mainfrom
fix/council-top5-harness

Conversation

@ranvier2d2

@ranvier2d2 ranvier2d2 commented Mar 28, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Desktop harness bundling: Electron .dmg now bundles the Elixir harness via Mix release, with port discovery and bootstrap pipe forwarding
  • Council top-5 harness fixes: 5 verified findings from multi-agent architecture review:
    1. Add Elixir tests to CI (erlef/setup-beam, mix test, dep cache) — gates 88 tests / 2,249 lines
    2. Supervisor :one_for_one:rest_for_one — Storage crash now cascades correctly to SnapshotServer
    3. Emit session/closed in terminate/2 for all 5 providers — fixes stale "running" snapshots after explicit stop
    4. Authenticate /api/* HTTP routes with harness_secret — parity with WebSocket auth
    5. Activate gap detection — replay_from_sql_or_empty returns {:gap,...} on SQL failure, activates dead recovery handler

Test plan

  • mix test — 88 tests, 0 failures (verified locally)
  • bun typecheck / bun run test / bun fmt / bun lint — all pass
  • CI runs Elixir tests for the first time via new workflow step
  • Verify /api/snapshot returns 401 without secret, 200 with ?secret=<harness_secret>
  • Verify explicit session stop shows :closed status in snapshot (not stale :running)

🤖 Generated with Claude Code


Open with Devin

Summary by CodeRabbit

  • New Features

    • Desktop application now bundles and manages the Elixir harness internally
    • Added API authentication to secure harness endpoints
    • Introduced council dispatch workflow for distributed task execution
  • Chores

    • Enhanced CI/build pipeline with Elixir toolchain integration and harness release bundling

…ap detection

1. Add Elixir tests to CI (erlef/setup-beam, mix test, dep cache)
2. Supervisor :one_for_one → :rest_for_one (Storage crash cascades correctly)
3. Emit session/closed in terminate/2 for all 5 providers (fixes stale snapshots)
4. Authenticate /api/* HTTP routes with harness_secret (parity with WebSocket auth)
5. Activate gap detection — replay_from_sql_or_empty returns {:gap,...} on SQL failure

All findings verified against current code via multi-agent council analysis.
88 Elixir tests pass, 0 failures.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Mar 28, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@ranvier2d2 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 5 minutes and 56 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 5 minutes and 56 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, 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 have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e478f3ae-5540-4989-aec0-fea909e3f66f

📥 Commits

Reviewing files that changed from the base of the PR and between 1fe109e and e44897f.

📒 Files selected for processing (5)
  • apps/desktop/src/main.ts
  • apps/harness/lib/harness/providers/cursor_session.ex
  • apps/harness/lib/harness/snapshot_server.ex
  • apps/harness/lib/harness_web/endpoint.ex
  • scripts/lib/t3-client.ts
📝 Walkthrough

Walkthrough

This PR integrates an Elixir harness release into the T3Code desktop application. It adds CI/build steps for Elixir toolchain and testing, implements desktop process management to launch and monitor a bundled harness, configures harness runtime and database paths, adds session closure event emissions across all provider sessions, enables release builds, and introduces new council dispatch tooling (council-dispatch.ts) and a WebSocket-based T3Client library for server communication.

Changes

Cohort / File(s) Summary
CI and Build Infrastructure
.github/workflows/ci.yml, scripts/build-desktop-artifact.ts, apps/harness/mix.exs
Added Elixir toolchain setup (OTP 27, Elixir 1.18) with caching and test execution; extended desktop artifact building to stage bundled harness release from apps/harness/_build/prod/rel/harness; added release configuration with ERTS inclusion and fixed cookie.
Desktop Harness Integration
apps/desktop/src/main.ts
Implemented harness process lifecycle management: resolveHarnessReleasePath(), startHarness(), and stopHarness() functions; updated bootstrap to write connection metadata (desktop-port, desktop-token) files; extended shutdown handlers to stop harness on exit/quit signals; integrated harness port/secret forwarding into backend configuration.
Harness Runtime Configuration
apps/harness/config/runtime.exs
Added dynamic database path configuration via environment variable T3CODE_HOME, deriving harness_db_path as T3CODE_HOME/harness/harness.db when present.
Harness Core Services
apps/harness/lib/harness/application.ex, apps/harness/lib/harness_web/endpoint.ex
Changed supervisor restart strategy from :one_for_one to :rest_for_one; added authentication middleware on /api/* routes validating secret from Authorization header or query parameter against harness_secret config.
Session Provider Termination
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
All session providers now emit "session/closed" event during GenServer termination (after marking process stopped/stopping).
Snapshot Recovery
apps/harness/lib/harness/snapshot_server.ex
Changed replay error handling to signal sequencing gaps ({:gap, seq, after_seq}) instead of returning empty events list on storage errors or process exits.
Council Tooling
scripts/council-dispatch.ts, scripts/lib/t3-client.ts
Introduced new council-dispatch.ts script for fire-and-forget council workflow initiation, parsing prompts and provider selection; added T3Client WebSocket library for local server communication with request/response correlation, auth token support, and push event routing.

Sequence Diagrams

sequenceDiagram
    participant Desktop as Desktop App
    participant Harness as Harness Process
    participant Backend as Backend Server
    participant FileSystem as File System

    Desktop->>Desktop: bootstrap()
    Desktop->>Harness: startHarness()
    Harness->>Harness: spawn harness release
    Harness-->>Desktop: harnessPort, harnessSecret
    Desktop->>FileSystem: write desktop-port file
    Desktop->>FileSystem: write desktop-token file
    Desktop->>Backend: startBackend(with harness config)
    Backend-->>Desktop: backendPort, backendAuthToken
    Desktop->>Desktop: application ready
    Note over Desktop,Harness: On shutdown...
    Desktop->>Harness: stopHarness()
    Harness->>Harness: terminate process
    Desktop->>Backend: stop backend
Loading
sequenceDiagram
    participant Script as council-dispatch.ts
    participant T3Client as T3Client
    participant Server as t3code Server
    participant Provider1 as Provider 1
    participant Provider2 as Provider 2

    Script->>T3Client: connect()
    T3Client->>Server: WebSocket handshake
    Server-->>T3Client: server.welcome push
    T3Client-->>Script: connected
    Script->>Script: parse CLI args (prompt, providers)
    Script->>T3Client: send(thread.create)
    T3Client->>Server: request with id
    Server-->>T3Client: response with id
    T3Client-->>Script: threadId
    Script->>T3Client: send(thread.turn.start, prompt)
    T3Client->>Server: dispatch to Provider 1
    T3Client->>Server: dispatch to Provider 2
    Server->>Provider1: execute with prompt
    Server->>Provider2: execute with prompt
    Script->>T3Client: disconnect()
    Script->>FileSystem: write manifest.json
    Script-->>Script: exit
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested labels

size:XXL

Poem

🐰 A harness springs forth from Elixir's spell,
Desktop and server now dance quite well,
Sessions emit their last goodbye song,
Councils dispatch when buffers run strong,
The rabbit hops onward—the journey's long! 🌙

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description covers the main changes and test plan, but does not follow the repository's PR template structure (What Changed / Why / Checklist sections). Restructure using the template sections (What Changed, Why, Checklist) for consistency with repository standards, though the content itself is substantive.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the two main components of this PR: desktop harness bundling and five verified harness fixes from council architecture review.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/council-top5-harness

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added size:XL vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels Mar 28, 2026

@devin-ai-integration devin-ai-integration 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.

Devin Review found 3 potential issues.

View 6 additional findings in Devin Review.

Open in Devin Review

Comment thread apps/desktop/src/main.ts
Comment thread scripts/council-dispatch.ts Outdated

// Desktop mode may not include bootstrapProjectId in welcome — look it up
if (!projectId) {
const snapshot = (await client.request("orchestration.getSnapshot", {})) as {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Calling non-existent client.request() method causes runtime TypeError

scripts/council-dispatch.ts:141 calls client.request("orchestration.getSnapshot", {}), but the T3Client class (scripts/lib/t3-client.ts:47-202) does not have a request method. The available methods are send, dispatch, connect, onPush, and disconnect. This will throw TypeError: client.request is not a function at runtime when the fallback project lookup path is reached. The scripts directory typecheck is skipped in CI (scripts/package.json has "typecheck": "echo 'scripts typecheck skipped in CI...'").

Suggested change
const snapshot = (await client.request("orchestration.getSnapshot", {})) as {
const snapshot = (await client.send("orchestration.getSnapshot", {})) as {
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread apps/desktop/src/main.ts
return false;
}

harnessPort = 4321;

@devin-ai-integration devin-ai-integration Bot Mar 28, 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.

🟡 Hardcoded harness port 4321 causes port conflict with multiple desktop instances

When starting a bundled harness release, harnessPort is hardcoded to 4321 at apps/desktop/src/main.ts:988. If a user launches multiple desktop instances, all harness processes attempt to bind the same port, causing the second (and subsequent) instances to fail. The backend port is properly dynamically allocated via NetService.reserveLoopbackPort() (apps/desktop/src/main.ts:1441-1444), but the harness doesn't receive the same treatment. The port should be dynamically allocated, similar to how the backend port is handled.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Matches the before-quit handler pattern. Prevents orphaned harness
child process if app.quit() fails after signal delivery.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
coderabbitai[bot]

This comment was marked as resolved.

ranvier2d2 and others added 2 commits March 28, 2026 17:32
…ap logging

1. Use Plug.Crypto.secure_compare for harness secret (timing attack mitigation)
2. Move session/closed emission after maybe_complete_turn in cursor terminate/2
   (prevents state-changed→ready firing after session/closed)
3. Log SQL replay failure reason before returning {:gap,...}

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added size:M and removed size:XL labels Mar 28, 2026
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ranvier2d2
ranvier2d2 merged commit c48e1c8 into main Mar 28, 2026
7 checks passed

@devin-ai-integration devin-ai-integration 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.

Devin Review found 2 new potential issues.

View 9 additional findings in Devin Review.

Open in Devin Review

Comment thread apps/desktop/src/main.ts
Comment on lines +1553 to 1554
stopHarness();
stopBackend();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 SIGINT/SIGTERM handlers stop harness before backend, opposite of before-quit handler

The new stopHarness() calls in the SIGINT and SIGTERM handlers are placed before stopBackend(), but the existing before-quit handler at apps/desktop/src/main.ts:1514-1515 uses the correct order: stopBackend() first, then stopHarness(). Since the backend is a client of the harness (it receives harnessPort and harnessSecret during bootstrap at line 1103-1105 and connects to it), killing the harness first leaves the backend with a dead upstream connection, potentially causing error storms, reconnect attempts, or unclean shutdown of provider sessions before the backend itself is terminated.

Suggested change
stopHarness();
stopBackend();
stopBackend();
stopHarness();
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread apps/desktop/src/main.ts
Comment on lines +1564 to 1565
stopHarness();
stopBackend();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 SIGTERM handler also stops harness before backend (same issue as SIGINT)

Same ordering bug as in the SIGINT handler — stopHarness() is called before stopBackend(), inconsistent with the before-quit handler at apps/desktop/src/main.ts:1514-1515 which correctly stops the backend (the harness client) before stopping the harness (the upstream service).

Suggested change
stopHarness();
stopBackend();
stopBackend();
stopHarness();
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant