fix: reuse parent worktree via parentLoopId, auto-inject event timestamps - #23
Conversation
…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; |
There was a problem hiding this comment.
[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) { |
There was a problem hiding this comment.
[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; |
There was a problem hiding this comment.
[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;
Code Review SummaryStatus: Approved Reviewers: Bug Hunter A, Bug Hunter B, Unified Auditor, Premise Reviewer, Gateway Core Architect Findings
MEDIUM Issues (consider)
Validation Stats
Recommendation: Approve — no blocking or high-priority issues. Consider the three medium items before merging. |
Summary
parentLoopId(deterministic naming:symphony/loop-{slugified-parentLoopId}) instead of relying onparentBranchNamebeing persisted in the DB — which was never populated for PLAN loops. Falls back toparentBranchNamefor backwards compat.postLoopEvent()auto-injectstimestampon every event, matching ECS harnessreportEvent()behavior. Fixes 400 errors from server validation requiring timestamps.resultfor all commands (not just EXECUTE), using camelCase field names (branchName,prUrl,prNumber) to match server'sextractPrSessionInfo()expectations.Root cause
REQUEST_CHANGES created a new worktree instead of reusing the PLAN worktree because
parentBranchNamewas never populated. The PLAN completed event didn't includebranchNameinresult, so the server never persisted it on the loop record. When the child loop tried to resolve parent info,branchNamewas null.Companion PR
parentLoopIdin the desktop dispatch payloadTest plan
branchNamein result