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

fix: reuse parent worktree via parentLoopId, auto-inject event timestamps - #23

Merged
thadeusb merged 2 commits into
mainfrom
fix/desktop-worktree-reuse-via-parent-loop-id
Mar 17, 2026
Merged

thadeusb merged 2 commits into
mainfrom
fix/desktop-worktree-reuse-via-parent-loop-id

Conversation

@thadeusb

Copy link
Copy Markdown
Contributor

Summary

  • Worktree reuse: EXECUTE/REQUEST_CHANGES now derives the parent's worktree path from parentLoopId (deterministic naming: symphony/loop-{slugified-parentLoopId}) instead of relying on parentBranchName being persisted in the DB — which was never populated for PLAN loops. Falls back to parentBranchName for backwards compat.
  • Event timestamps: postLoopEvent() auto-injects timestamp on every event, matching ECS harness reportEvent() behavior. Fixes 400 errors from server validation requiring timestamps.
  • Completed event branchName: Includes worktree branch in result for all commands (not just EXECUTE), using camelCase field names (branchName, prUrl, prNumber) to match server's extractPrSessionInfo() expectations.

Root cause

REQUEST_CHANGES created a new worktree instead of reusing the PLAN worktree because parentBranchName was never populated. The PLAN completed event didn't include branchName in result, so the server never persisted it on the loop record. When the child loop tried to resolve parent info, branchName was null.

Companion PR

  • closedloop-ai/symphony-alpha — sends parentLoopId in the desktop dispatch payload

Test plan

  • Run PLAN → REQUEST_CHANGES on desktop, verify REQUEST_CHANGES reuses PLAN's worktree (log: "Reusing parent worktree via parentLoopId")
  • Run PLAN → EXECUTE, verify same worktree reuse
  • Verify no 400 errors on event POST (timestamp auto-injected)
  • Verify completed events include branchName in result

…amps

Three fixes for desktop loop parity:

1. Worktree reuse: EXECUTE/REQUEST_CHANGES now derives the parent's
   worktree path from parentLoopId (deterministic naming) instead of
   relying on parentBranchName being persisted in the DB. Falls back
   to parentBranchName for backwards compat.

2. Event timestamps: postLoopEvent() auto-injects timestamp on every
   event, matching ECS harness reportEvent() behavior. Fixes 400
   errors from server validation.

3. Completed event branchName: include worktree branch in result for
   all commands (not just EXECUTE), using camelCase field names to
   match server's extractPrSessionInfo() expectations.
// EXECUTE/REQUEST_CHANGES: reuse parent's worktree.
// Derive the parent's worktree path from parentLoopId (deterministic naming),
// falling back to parentBranchName for backwards compat.
const parentStableId = body.parentLoopId ? slugifyLoopId(body.parentLoopId) : null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[MEDIUM] Correctness

[P2] parentLoopId is not validated as a UUID, unlike loopId

Recommendation: Add UUID format validation for parentLoopId when it is provided, consistent with the existing loopId check: if (body.parentLoopId && !/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/i.test(body.parentLoopId)) { json(context, 400, { error: 'parentLoopId must be a valid UUID' }); return; }

const parentStableId = body.parentLoopId ? slugifyLoopId(body.parentLoopId) : null;

stdio: "pipe",
timeout: 5_000,
}).trim();
if (branch) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[MEDIUM] Correctness

[P2] git rev-parse may return literal "HEAD" in detached-HEAD state, stored as branchName

Recommendation: Add a check for the detached HEAD sentinel: if (branch && branch !== 'HEAD') { result.branchName = branch; }

if (branch) {
        result.branchName = branch;
      }

result.prUrl = execResult.pr_url;
result.prNumber = execResult.pr_number;
result.branchName = execResult.branch_name;
result.has_changes = execResult.has_changes ?? false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[MEDIUM] Correctness

[P2] has_changes not renamed to hasChanges while sibling fields were renamed to camelCase

Recommendation: Rename result.has_changes to result.hasChanges on line 790 to match the camelCase convention applied to the other three fields in the same block.

result.prUrl = execResult.pr_url;
    result.prNumber = execResult.pr_number;
    result.branchName = execResult.branch_name;
    result.has_changes = execResult.has_changes ?? false;

@closedloop-ai-stage

Copy link
Copy Markdown

Code Review Summary

Status: Approved

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

Findings

Severity Count
Blocking 0
High 0
Medium 3

MEDIUM Issues (consider)

  1. [P2] [symphony-loop.ts:948] parentLoopId is not validated as a UUID, unlike loopId
  2. [P2] [symphony-loop.ts:803] git rev-parse may return literal "HEAD" in detached-HEAD state, stored as branchName
  3. [P2] [symphony-loop.ts:790] has_changes not renamed to hasChanges while sibling fields were renamed to camelCase

Validation Stats

  • Agent failures: 0 partitions skipped
  • Cross-file grouped: 0 findings consolidated
  • Discarded: 1 duplicate

Recommendation: Approve — no blocking or high-priority issues. Consider the three medium items before merging.

@thadeusb
thadeusb merged commit 1a5286a into main Mar 17, 2026
1 check passed
@thadeusb
thadeusb deleted the fix/desktop-worktree-reuse-via-parent-loop-id branch March 17, 2026 08:21
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.

1 participant