feat(isolation): re-enable worktree isolation with real merge-back (#787)#867
Conversation
) Re-enables --isolation worktree for the single-run path (cf work start) with auto-commit + merge-back, reversing the #714 interim hard-reject. - worktrees: TaskWorktree.auto_commit() stages+commits worktree changes - sandbox/context: rebased_workspace() keeps state on main / code on worktree; ExecutionContext gains merge_back/preserve; validate_isolation no longer rejects WORKTREE (CLOUD still raises); create_execution_context builds a real worktree (not registered so orphan cleanup can't nuke a preserved branch) - builtin adapters (#715): react/plan run against workspace_path - verification_wrapper (#716): gates + quick-fixes run against the worktree - runtime: merge-back on success; conflict -> blocker + preserve; fail -> preserve - cli: work start accepts worktree; work batch run rejects it (subprocess can't reach the gitignored .codeframe DB from a worktree) with a batch-specific message Tests: new test_worktree_isolation.py (file-on-base after success, conflict->blocker +preserve, fail->preserve, #715/#716 threading); updated sandbox/exit-code tests from the #714 disable contract to the #787 enable contract.
🔍 Precision bug-hunting review — 2 defects foundScoped to concrete defects only (logic/data-loss/error-path). Style, naming, architecture, and test coverage are covered by the general review and intentionally not repeated here.
1.
|
Review: #867 — re-enable worktree isolation with real merge-backSolid design overall — Major — merge-back races on the shared main working tree
Major — merge conflict downgrades
|
Addresses defects found by opencode/GLM + CI review bots: - auto_commit fails loud (critical): git add/commit now raise on failure instead of reporting success — a failed stage/commit preserves the branch rather than merging nothing then deleting it (the #714 silent-data-loss class). - Preserved-branch collision (major): create_execution_context moved inside execute_agent's try + a clear ValueError when a leftover cf/<task> branch or worktree exists — a retry becomes a handled FAILED (preserved), not a stranded IN_PROGRESS run. - engine_stats miscount (major): re-sync agent_status to the final state so a merge-back conflict is recorded BLOCKED, not COMPLETED. - Concurrent merge-back (major): serialize the checkout+merge on the shared main tree with a cross-process flock (_main_tree_lock). - Dirty-tree checkout failure now routes through the merge-back error -> blocker + preserve (try/except around merge_back()). Tests: auto_commit-raises regression, preserved-branch-collision, registry-empty assertion; fixed mock-workspace path mismatches in test_builtin / test_verification_wrapper / test_engine_registry_extended (rebased_workspace calls dataclasses.replace, which needs workspace_path==repo_path for the mock no-op).
Review response — all Critical/Major findings addressed (commit acc06b4)Thanks to both reviews (opencode/GLM + CI bots) — they converged on the same real defects.
Verified non-issues (agreed): |
Demo — #787 acceptance criteria → outcome evidenceThis is a CLI/core feature (no web surface); the evidence is the integration suite
|
Follow-up review — verified the hardening commit (acc06b4)I re-read the full base→head diff (
The one minor item from my first pass — Two small things worth a look (optional, not blocking)
No new correctness, security, or data-loss issues found in the hardening commit itself — |
Summary
Re-enables
--isolation worktreefor the single-run path (cf work start)with real merge-back, reversing the interim hard-reject from #714 (#786). Closes #787.
Each worktree run now:
blocker/event state stays in the main repo's
.codeframeDB, andcf/<task_id>back tothe base branch; on conflict or failure the branch is preserved for recovery
(never silently discarded — the [P0.3] Worktree isolation must not silently discard external-engine work (
merge_backis dead code) #714 bug).Scope decision (single-run only)
Batch (
cf work batch run --isolation worktree) stays rejected at the CLI.The batch path spawns a subprocess with
cwd=worktree, and.codeframe/isgitignored, so a child can't reach the main task DB from a worktree. Solving that
is a separate follow-up; this PR delivers every acceptance criterion in #787,
which are all single-run.
How it works
rebased_workspace(ws, worktree) = dataclasses.replace(ws, repo_path=worktree)—code I/O + gates follow the worktree;
state_dir/db_pathstay on main. Merge-backis driven by
runtime.execute_agentfrom the terminal run status.Changes
TaskWorktree.auto_commit()— stage + commit worktree changes.rebased_workspace();ExecutionContextgainsmerge_back/preserve;validate_isolationno longer rejects WORKTREE (CLOUDstill raises);
create_execution_contextbuilds a real worktree + wires hooks(intentionally not registered in
WorktreeRegistryso orphan cleanup can'tnuke a preserved branch).
workspace_path— isolation is a silent no-op #715): react/plan adapters build the agent againstthe worktree via
rebased_workspace.VerificationWrapperruns gates/quick-fixes against the main repo, not the worktree the agent modified #716): gates + quick-fixes run againstthe worktree; blockers still land in the main-repo DB.
BLOCKED + preserve; failure/blocked/exception → preserve.
work startaccepts worktree;work batch runexplicitly rejectsit with a batch-specific message.
Tests
test_worktree_isolation.py(new):rebased_workspaceinvariant,auto_commit,and end-to-end
execute_agent— file lands on base after success + branchcleaned, conflict → blocker + branch preserved, failure → branch preserved;
plus [P0.4] Builtin (react/plan) engines ignore the worktree
workspace_path— isolation is a silent no-op #715/[P0.5]VerificationWrapperruns gates/quick-fixes against the main repo, not the worktree the agent modified #716 adapter-threading unit tests.test_sandbox_context.py/test_work_exit_codes.py: updated from the [P0.3] Worktree isolation must not silently discard external-engine work (merge_backis dead code) #714disable contract to the [P2.24] Re-enable worktree isolation with real merge-back (+auto-commit, workspace_path, verify) #787 enable contract.
Review hardening (post-review, in this PR)
Two independent reviews (opencode/GLM + CI bots) converged on real defects, all fixed here:
git add/commitnow fail loud —a failed stage/commit raises → run preserved, never merged-then-deleted.
execute_agent's try + a clear actionable error, so a leftovercf/<task>yieldsa handled FAILED (branch preserved), not a stranded IN_PROGRESS run.
agent_statusre-synced to the final state so amerge-back conflict is recorded as BLOCKED, not COMPLETED.
cross-process
flock(_main_tree_lock).try/exceptaroundmerge_back()), matching the docs.Known limitations
merge on the same repo is serialized (flock); a dirty/conflicting main tree
surfaces as a blocker with the branch preserved rather than merging. Clean,
sequential
cf work startruns (the norm) are unaffected.