Skip to content
This repository was archived by the owner on Jun 8, 2026. It is now read-only.

Desktop loop dispatch: electron harness - #16

Merged
thadeusb merged 2 commits into
mainfrom
feature/desktop-loop-dispatch
Mar 16, 2026
Merged

thadeusb merged 2 commits into
mainfrom
feature/desktop-loop-dispatch

Conversation

@thadeusb

Copy link
Copy Markdown
Contributor

Summary

  • Create symphony-loop.ts — full electron-side loop handler for all 4 commands (PLAN, EXECUTE, REQUEST_CHANGES, DECOMPOSE)
  • Worktree management: create for PLAN, reuse parent branch for EXECUTE/REQUEST_CHANGES, temp dir for DECOMPOSE
  • Per-command artifact writing and output reading
  • Git operations for EXECUTE: stage, commit, push, PR creation with existing-PR retry handling
  • Process spawning with close/error event handlers and onceComplete dedup guard
  • SSRF-hardened validateApiBaseUrl covering IPv4, IPv6, IPv4-mapped IPv6, loopback, and all RFC-1918 ranges
  • Sentinel pattern (runningLoops.set(loopId, -1)) for atomic conflict detection with try/finally cleanup
  • Kill handler with sentinel-aware PID guard (returns 409 for initializing loops)
  • Pre-flight binary checks for both run-loop.sh and claude CLI
  • Register routes in router.ts, add symphony_loop + symphony_loop_kill operation IDs in app.ts

Companion PR: closedloop-ai/symphony-alpha (API + frontend — same branch name)

Test plan

  • pnpm typecheck passes (verified locally)
  • PLAN: dispatched command spawns run-loop.sh, outputs uploaded to API
  • EXECUTE: spawns run-loop.sh, commits + pushes + creates PR, uploads results
  • DECOMPOSE: spawns claude -p via stdin, uploads features.json
  • Kill: running process terminated via SIGTERM → SIGKILL escalation
  • Concurrent duplicate loopId returns 409
  • Invalid apiBaseUrl (private IP, loopback, IPv6-mapped) returns 400

🤖 Generated with Claude Code

- Create symphony-loop.ts with full loop handler (PLAN/EXECUTE/REQUEST_CHANGES/DECOMPOSE)
- Worktree management: create for PLAN, reuse parent for EXECUTE/REQUEST_CHANGES
- Per-command artifact writing and output reading
- Git operations for EXECUTE (stage, commit, push, PR creation with retry handling)
- Process spawning with close/error event handlers and dedup guards
- SSRF-hardened apiBaseUrl validation (IPv4, IPv6, IPv4-mapped IPv6, loopback)
- Sentinel pattern for atomic loopId conflict detection
- Kill handler with sentinel-aware PID guard
- Pre-flight binary checks for both run-loop.sh and claude CLI
- Register routes in router.ts, add symphony_loop + symphony_loop_kill operation IDs

Testing: pnpm typecheck passes, manual review of all error paths
Risks: New electron routes, process lifecycle management
Comment thread apps/desktop/src/server/operations/symphony-loop.ts
Comment thread apps/desktop/src/server/operations/symphony-loop.ts
Comment thread apps/desktop/src/server/operations/symphony-loop.ts
Comment thread apps/desktop/src/server/operations/symphony-loop.ts
Comment thread apps/desktop/src/server/operations/symphony-loop.ts Outdated
Comment thread apps/desktop/src/server/operations/symphony-loop.ts Outdated
Comment thread apps/desktop/src/server/operations/symphony-loop.ts
Comment thread apps/desktop/src/server/operations/symphony-loop.ts
@closedloop-ai-stage

Copy link
Copy Markdown

Code Review Summary

Status: Changes Requested

Reviewers: Bug Hunter A, Bug Hunter B, Unified Auditor, Premise Reviewer, Gateway Core Architect

Findings

Severity Count
Blocking 1
High 2
Medium 5

BLOCKING Issues (must fix)

  1. [P0] [symphony-loop.ts:769] worktreeDir from findWorktreeForBranch not validated against sandbox before file writes — a git worktree registered outside the sandbox base directory can be used for arbitrary file writes. All other handlers call assertPathAllowed on worktree paths; this one skips it entirely.

HIGH Issues (should fix)

  1. [P1] [symphony-loop.ts:77] closedLoopAuthToken in request body is captured by the activity event logger — the router unconditionally captures the full request body as capturedRequestBody and emits it to all onActivityEvent subscribers, persisting the bearer token in the activity log on every loop call. Move to a request header.

  2. [P1] [symphony-loop.ts:856] Synchronous throw in spawn block leaves HTTP response unsent — if openSync or spawn throws, the inner finally closes logFd but never calls json(), leaving the client hanging with a loop already marked as 'started' in the API.

MEDIUM Issues (consider)

  1. [P1] [symphony-loop.ts:101] SSRF validation only checks hostname string, not resolved IP — DNS rebinding allows an attacker-controlled domain resolving to a private IP to bypass all hostname checks.

  2. [P2] [symphony-loop.ts:864] promptFd file descriptor leaks if spawn throws in the DECOMPOSE path — closeSync(promptFd) at line 875 is skipped when spawn throws; move close to the finally block.

  3. [P2] [symphony-loop.ts:342] writeArtifactsForExecute is a strict subset of writeArtifactsForRequestChanges (~85% identical) — can be removed by calling the latter with no prompt argument.

  4. [P2] [symphony-loop.ts:224] resolveLoopWorktreeDir duplicates resolveWorktreeDir from symphony-utils.ts — structurally identical except for the loop- prefix; consolidate with an optional prefix parameter.

  5. [P2] [symphony-loop.ts:151] validateApiBaseUrl does not block IPv6 ULA (fc00::/7) and link-local (fe80::/10) addresses — completes the SSRF mitigation for IPv6 private ranges.

Validation Stats

  • Total from agents: 8
  • Validated: 8
  • Discarded: 0
  • Agent failures: 0 partitions skipped
  • Cross-file grouped: 0

Recommendation: Address blocking and high issues before merging. The sandbox escape (P0) and auth token leakage (P1) are the critical items.

@thadeusb
thadeusb merged commit 9d6a1a6 into main Mar 16, 2026
1 check passed
@thadeusb
thadeusb deleted the feature/desktop-loop-dispatch branch March 16, 2026 17:26
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants