feat(plan): create per-project sandboxes for multi-project plans #10828
Dismiss Review
Are you sure you want to dismiss this review?
Labels
Clear labels
auto/needs-reevaluation
controller-managed
overdue
auto/blocked-by-deps
auto/ci-timeout
auto/claimed-implementer
auto/claimed-merge
auto/claimed-reviewer
auto/driver-down
auto/invariant-violation
auto/last-attempt-tier-0
auto/last-attempt-tier-1
auto/last-attempt-tier-2
auto/last-attempt-tier-min
Automation Tracking
auto/needs-conflict-resolution
auto/needs-implementer
auto/postmortem
auto/ready-to-merge
auto/restart-throttled
auto/revert
auto/sentinel
auto/stale-inactivity
auto/unstable
Blocked
Needs Feedback
Signed-off: Owner
Signed-off: Scrum Master
Signed-off: Tech Lead
Spike
Controller deferred this PR; awaiting Phase 6+ scope-evaluator or operator re-enablement.
Auto-agents controller manages this PR/issue (see tools/controller/deploy/RUNBOOK.md). Remove this label to abandon controller management.
PR blocked by an open issue dependency. Operator must close the dep (or remove the dependency link) before the merge driver can act. Auto-cleared by merge_drive when no open deps remain.
Most recent merge cycle hit CI timeout. Driver excludes this PR while last merge_cycle row is < 30 min old; label persists thereafter as visible history.
Currently being processed by an implementer worker.
Currently being processed by the merge driver.
Currently being processed by a reviewer worker.
Merge driver heartbeat stale; pipeline halted. Closed automatically on next clean tick.
Detected master commit violating the strict merge invariant. Tracked as an issue (not a PR label); kept here for label completeness.
In-cycle escalation: most recent attempt ran at the Tier 0 slot (`tier-0`). Slot's model defined in .opencode/models/tiers.yaml.
In-cycle escalation: most recent attempt ran at the Tier 1 slot (`tier-1`). Slot's model defined in .opencode/models/tiers.yaml.
In-cycle escalation: most recent attempt ran at the Tier 2 slot (`tier-2`). Slot's model defined in .opencode/models/tiers.yaml. Gated behind IMPLEMENTER_ESCALATION_TIER2_ENABLED.
In-cycle escalation: most recent attempt ran at the Tier -1 slot (`tier-min`). Slot's model defined in .opencode/models/tiers.yaml. Suffix is ``-min`` (not ``--1``) so the Forgejo UI reads naturally.
Tracking issues used by the AI Automation system for agents to communicate and report.
Rebase conflict needs LLM conflict-resolver.
Failing CI needs implementer attention.
Documenting a driver incident or rollback.
Reviewer has APPROVED this PR and no later REQUEST_CHANGES is outstanding. The merge driver requires this label to even consider a PR for merging. Set by the reviewer worker on APPROVE; cleared on REQUEST_CHANGES.
Train repeatedly lost master-tempo races. Driver excludes via merge_cycle until cooldown elapses; label persists as visible history.
Revert PR backing out an invariant violation. Fast-tracked through the merge driver.
Sentinel PR duplicated from upstream into a personal fork by tools/duplicate_prs_to_fork.py for pipeline testing. Lives only in the fork; the canonical pipeline never sees it.
No implementer activity for N days. Flagged for human review. Auto-cleared on next push to head branch.
Repeatedly fails on current master (>= 3 ci-fail-on-rebased-sha releases in 12 h). Excluded from driver until human triage.
A ticket in a blocked state and unable to complete until some other task is completed first.
Bounty
$100
A bounty of $100 for any open-source contributor who provides a MR that solves this issue
Bounty
$1000
A bounty of $1000 for any open-source contributor who provides a MR that solves this issue
Bounty
$10000
A bounty of $10000 for any open-source contributor who provides a MR that solves this issue
Bounty
$20
A bounty of $20 for any open-source contributor who provides a MR that solves this issue
Bounty
$2000
A bounty of $2000 for any open-source contributor who provides a MR that solves this issue
Bounty
$250
A bounty of $250 for any open-source contributor who provides a MR that solves this issue
Bounty
$50
A bounty of $50 for any open-source contributor who provides a MR that solves this issue
Bounty
$500
A bounty of $500 for any open-source contributor who provides a MR that solves this issue
Bounty
$5000
A bounty of $5000 for any open-source contributor who provides a MR that solves this issue
Bounty
$750
A bounty of $750 for any open-source contributor who provides a MR that solves this issue
MoSCoW
Could have
Could have feature in order to satisfy the epic/legendary.
MoSCoW
Must have
Must have feature in order to satisfy the epic/legendary.
MoSCoW
Should have
Should have feature in order to satisfy the epic/legendary.
There are questions in the ticket that can not be completed until the project owner provides clarity.
Points
1
1 man-hours worth of work for an expert with no learning curve.
Points
13
13 man-hours worth of work for an expert with no learning curve.
Points
2
2 man-hours worth of work for an expert with no learning curve.
Points
21
21 man-hours worth of work for an expert with no learning curve.
Points
3
3 man-hours worth of work for an expert with no learning curve.
Points
34
34 man-hours worth of work for an expert with no learning curve.
Points
5
5 man-hours worth of work for an expert with no learning curve.
Points
55
55 man-hours worth of work for an expert with no learning curve.
Points
8
8 man-hours worth of work for an expert with no learning curve.
Points
88
88 man-hours worth of work for an expert with no learning curve.
Priority
Backlog
This ticket has backlogged priority and is not to be worked on yet
Priority
CI Blocker
Critical priority issue that blocks CI/CD pipeline and prevents PR merges
Priority
Critical
The priority is critical
Priority
High
The priority is high
Priority
Low
The priority is low
Priority
Medium
The priority is medium
When an epic or legendary is in review it must be signed off by owner, tech lead, and scrum master before being marked as completed.
When an epic or legendary is in review it must be signed off by owner, tech lead, and scrum master before being marked as completed.
When an epic or legendary is in review it must be signed off by owner, tech lead, and scrum master before being marked as completed.
A ticket for learning a tool or technology that is needed to be able to do future planning and design.
State
Completed
The ticket has been fully implemented, completed, and merged with the source code. This label should only be applied once a ticket is closed.
State
Duplicate
A ticket that represents the same content as an existing ticket.
State
In Progress
A ticket that is actively being developed.
State
In Review
A ticket that has had some code completed to implement but is waiting to pass peer review and is not yet merged in.
State
Paused
This ticket's work started but wasn't finished. It's on hold (likely in a feature branch) and will be resumed later, either due to a blocker or a delay.
State
Unverified
All new tickets start in this state. A developer may set it to show the ticket is unverified. This means we haven't agreed to work on it. It will either move to a verified state or be closed as wontdo.
State
Verified
The issue has been verified by a developer as legitimate. It will be worked on and verified tickets are now considered part of the backlog.
State
Wont Do
This ticket has been decided it wont be done. This may mean the bug has been determined to not be real (cant verify) or the feature is one we have decided we dont want to adopt.
Type
Automation
Any edits or discussion about the AI automated coding system.
Type
Bug
Something that doesnt work as intended.
Type
Discussion
Anytime a ticket represents a discussion about a subject and doesnt fall into one of the other categories.
Type
Documentation
An error or improvement needed in the documentation.
Type
Epic
Any first tier epic. That is, an epic which contains only issues as children and will not have sub-epics.
Type
Feature
Some new functionality not present.
Type
Legendary
A type of Epic which will contain other Epics.
Type
Refactor
A code change that restructures existing code without changing its external behavior.
Type
Support
Someone needs help using the project.
Type
Task
A generic task that doesnt fit into the other type categories.
Type
Testing
Work exclusively focusing on fixing or expanding testing.
Projects
Clear projects
No project
Assignees
aditya (Aditya Chhabra)
aleenaumair (Aleena Umair)
brent.edwards (Brent Edwards)
CoreRasurae (Luis Mendes)
drew (Drew Morris)
eugen.thaci (Eugen Thaci)
freemo (Jeffrey Phillips Freeman)
HAL9000 (HAL 9000)
HAL9001 (HAL9001)
hamza.khyari (Hamza Khyari)
hurui200320 (Rui Hu)
justin.morris
khird (Kyle Hird)
org.cleveragents
Clear assignees
No Assignees
HAL9000
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: cleveragents/cleveragents-core#10828
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Delete Branch "feature/multi-project-sandbox"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Per spec §19310-19312, each resource gets its own sandbox and Apply commits each sandbox separately. Previously, multi-project plans only created a worktree for the first project's resource — changes for other projects were lost.
How it works
_create_sandbox_for_plancreates a worktree for each git-checkout resource (not just the first)_route_sandbox_files_to_worktreesmoves files to the correct worktree by matching against each resource'sgit ls-filesoutput_apply_sandbox_changesmerges each worktree separately with per-resource Apply Summary panelsSingle-resource plans are fully backward compatible.
Testing
m1-plan-lifecycle-okCloses #7270
6ffb251aa9to2e25cb985e2e25cb985etof2916267eef2916267eeto09788e189d09788e189dto9389287eca@HAL9000 rebase this PR
Hi @hamza.khyari, thanks for this PR. Overall the implementation follows the spec, but I found a critical issue with the partial apply logic in _apply_sandbox_changes:
I’ve also noticed the spec reference in the docstring (§13241-13276) doesn’t match the new multi-project sandbox sections (§19310-19313) – please update to the correct spec sections.
Suggestions:
lrtolinked_resourcefor readability.Automated by CleverAgents Bot
Supervisor: PR Review | Agent: pr-review-worker
PR Review: !10828 (Ticket #7270)
Verdict: ❌ Request Changes
Two critical issues and four major issues must be resolved before this PR can merge. The core sandbox architecture is sound, but there is a data-loss bug in the file routing algorithm, a serious commit hygiene violation that hides unrelated deletions, and several resource-leak paths.
Critical Issues
C1 — File routing silently loses primary-project changes when files share the same relative path
File:
src/cleveragents/cli/commands/plan.py, lines 1836–1867Problem:
_route_sandbox_files_to_worktreeschecks whether each file in the primary sandbox exists in any non-primary resource'sgit ls-filesoutput. If it does, the file is moved (deleted from primary) to the secondary worktree — without first checking whether the file also belongs to the primary project. In practice, many files share the same relative path across projects (README.md,setup.py,pyproject.toml,src/__init__.py,.gitignore, etc.). When the LLM modifies such a file for the primary project, the routing algorithm will silently move it to the secondary project's worktree, losing the primary project's changes entirely.Example: Project Alpha (primary) and Project Beta both have
src/utils.py. LLM modifiessrc/utils.pyfor Alpha. Routing sees it in Beta's file list → moves it to Beta → Alpha's changes are gone.Recommendation: Build the primary resource's file list as well. Only move a file to a secondary worktree if it matches that secondary resource's file list and does not exist in the primary resource's file list:
C2 — Atomic commit violation: unrelated changes bundled into the feature commit
File: Entire commit on
feature/multi-project-sandboxProblem: The single commit bundles at least 6 unrelated change sets alongside the multi-project sandbox feature, violating CONTRIBUTING.md §85–98 ("One logical change per commit"). The unrelated changes include:
autonomy_guardrail_atomic_load.feature(108 lines) + step file (383 lines)context_analysis_engine.py(328 lines),context analyzeCLI command, feature + step files (~787 lines)lsp/runtime.py+ feature/step files (~393 lines)tui/widgets/prompt.py,tui/app.py, deletes feature/step files (~239 lines)ca-uat-tester.mdNet effect: ~2,500 lines of unrelated code deleted (including a security feature) hidden inside a feature commit. The commit is not cleanly revertible and breaks
git bisect.Recommendation: Remove all unrelated changes from this branch. The commit should contain only: changes to
plan.py, the newfeatures/multi_project_sandbox.feature,features/steps/multi_project_sandbox_steps.py, and any directly related test/config updates. Each unrelated change set must go through its own issue, commit, and PR.Major Issues
M1 —
_create_sandbox_for_plancallscleanup_stalein a loop, destroying previously-created sandboxes for shared reposFile:
src/cleveragents/cli/commands/plan.py, lines 1491–1499Problem: For each resource,
cleanup_stale(resource.location, plan_id)is called beforesandbox.create(plan_id). All sandboxes use the same branch namecleveragents/plan-{plan_id}. If two projects link to the same git repository, the second iteration'scleanup_stalewill delete the branch and worktree just created by the first iteration. The first_SandboxInfoentry then points to a destroyed worktree.Recommendation: Track which
(resource.location, plan_id)pairs have already been processed and skipcleanup_stalefor duplicates, or use resource-specific branch names (e.g.,cleveragents/plan-{plan_id}-{resource_id}).M2 —
_cleanup_sandbox_for_planearly-returns after the first resource, leaking all subsequent sandboxesFile:
src/cleveragents/cli/commands/plan.py, lines 1423–1424Problem: The function has
returnimmediately after the first successfulcleanup_stalecall. With multi-project sandboxes, only the first resource's stale sandbox is ever cleaned up; all others are leaked.Recommendation: Replace
returnwithcontinueso the loop cleans up all resources:M3 — No cleanup of already-created sandboxes when creation fails partway through
File:
src/cleveragents/cli/commands/plan.py, lines 1495–1507Problem:
sandbox.create(plan_id)is not wrapped in a try/except. If creation succeeds for the first N−1 resources but raises for the Nth, the exception propagates out of_create_sandbox_for_plan. The caller has no reference to the partially-created sandboxes, so their git worktrees and branches are never cleaned up.Recommendation: Wrap the creation call in a try/except that cleans up all previously-created sandboxes on failure before re-raising:
M4 — No cleanup of sandboxes when
execute_planfails after creationFile:
src/cleveragents/cli/commands/plan.py, lines 2637–2770Problem: After
_create_sandbox_for_plansucceeds, operations likeexecutor.run_execute(),_route_sandbox_files_to_worktrees(), and_commit_worktree_changes()can fail. The broad exception handlers at lines 2750–2770 print an error and raisetyper.Abort()but never callsandbox_obj.cleanup()on the created sandboxes, leaving git worktrees and branches behind on every failed execution.Recommendation: Add a
finallyblock to clean up sandboxes. SinceGitWorktreeSandbox.cleanup()is already idempotent (returns immediately if alreadyCLEANED_UP), it is safe to call unconditionally:Summary
The multi-project sandbox architecture is well-conceived and the spec compliance is solid (§19310–§19313 all satisfied). The single-project path is fully backward compatible. However, the PR cannot merge as-is for two reasons:
The four major issues (M1–M4) are resource-leak problems on failure paths that should be fixed but are less urgent than C1 and C2.
9389287ecato0653048648All review findings addressed:
git ls-files, skip files that exist in primary before moving to secondarycleanup_stalein loop destroys previously-created sandboxesprocessed_reposset, skip duplicate repo paths. Only cleanup stale for first resource._cleanup_sandbox_for_planearly-returns after first resourcereturntocontinue— cleans up all resourcessandbox.create()in try/except, cleans up all previously-created sandboxes before re-raisingfinallyblock with idempotentsandbox_obj.cleanup()for all sandbox_infos.sandbox_infosinitialized beforetryto avoid unbound variable.lrtolinked_resourcecontinuenotreturn— merge failures are per-resource, loop continuesLint passes, typecheck 0 errors, M1 E2E passes, 6 Behave scenarios pass. Ready for re-review.
PR Review: !10828 (Ticket #7270)
Verdict: ❌ Request Changes
Three blocking issues remain after the author's latest push. All previously-reported critical and major issues (C1, C2, M1–M4) were addressed, but the fixes introduced two new major bugs and the test suite has critical coverage gaps that leave the most important fix unprotected against regression.
Critical Issues
None — no spec violations or data-loss bugs in the happy path.
Major Issues
M-NEW-1 —
cleanup_staleskipped for 2nd+ distinct repos, breaking plan re-executionFile:
src/cleveragents/cli/commands/plan.py(in_create_sandbox_for_plan)Problem: The M1 fix used an
if not sandboxes:guard to callcleanup_staleonly for the very first resource. This prevents the original M1 bug (destroying a sandbox just created for the same repo), but it also preventscleanup_stalefrom running on the 2nd, 3rd, etc. resources that are in different repos. If a previous execution of the same plan left stale branches in those repos,sandbox.create()will fail because the branch already exists — and the entire multi-project plan becomes un-re-runnable.Recommendation: Call
cleanup_staleunconditionally for every distinct repo. Theprocessed_reposdedup already handles the same-repo case:M-NEW-2 — Silent data loss when primary
git ls-filesfails (C1 fix bypass)File:
src/cleveragents/cli/commands/plan.py, lines 1867–1868 (in_route_sandbox_files_to_worktrees)Problem: The C1 fix works by building a
primary_filesset and skipping any file that belongs to the primary project. However, ifgit ls-filesfails for the primary resource (git lock, permission error, timeout, disk I/O), theexcept Exceptionhandler setsprimary_files = set(). An empty set means the guardif rel_path in primary_files: continuenever triggers — every file matching a secondary resource's list gets moved away from the primary sandbox, silently destroying all primary-project changes. This is the exact data-loss scenario C1 was designed to prevent, now reachable via any transient git error.Recommendation: Fail safe — if the primary file list cannot be built, abort routing entirely rather than proceed with an empty set:
M-TEST-1 — C1 fix has zero test coverage for its target scenario (shared relative paths)
File:
features/multi_project_sandbox.feature, Scenario 3Problem: The routing scenario uses
src/app.py(alpha) andsrc/api.py(beta) — files unique to each project. The C1 fix was specifically designed to protect files that share the same relative path across projects (e.g.,README.md,setup.py). The existing test would pass even if theif rel_path in primary_files: continueguard were deleted entirely. Combined with M-NEW-2 (the bypass path), the C1 fix has no regression protection.Recommendation: Add a scenario where both projects contain a file at the same relative path (e.g.,
README.md), verify that after routing the file remains in the primary sandbox and is not moved to the secondary.M-TEST-2 — Per-project Apply Summary panels not asserted (ticket DoD item)
File:
features/multi_project_sandbox.feature, Scenario 5Problem: The ticket's Definition of Done explicitly requires "Apply merges all sandboxes, showing per-project summaries." The test captures console output but never asserts on it. The Apply Summary panels could be completely absent and the test would still pass.
Recommendation: Add
Thensteps asserting the console output contains an Apply Summary panel for each project name.M-TEST-3 — Partial apply scenario cannot distinguish "continued" from "skipped"
File:
features/multi_project_sandbox.feature, Scenario 6Problem: The scenario asserts alpha was merged and the return value is
False, but does not verify beta's state. If the implementation silently skipped beta entirely, the test would still pass. The test cannot prove the function actually attempted beta's merge.Recommendation: Add an assertion that beta's content is unchanged (merge was attempted and failed, not skipped).
Summary
The author resolved all 6 issues from the previous review round (C1, C2, M1–M4) and the commit is now clean and properly scoped. The core multi-project sandbox architecture is sound and spec-compliant (§19310–§19313 all satisfied). Single-project backward compatibility is maintained.
However, five blocking issues remain:
cleanup_staleis now only called for the first resource, leaving stale branches in all other repos and breaking plan re-execution for multi-project plans.git ls-filesfailure on the primary resource degrades back to the original data-loss behavior.Automated by CleverAgents Bot
Reviewer: Rui Hu | Agent: rui-review-pr
0653048648to7657574f56All 5 findings from the second review addressed:
cleanup_staleskipped for 2nd+ distinct reposif not sandboxesguard —cleanup_stalenow runs for every distinct repo.processed_reposdedup handles the same-repo case.primary_fileson git failure → data loss bypassprimary_files = set()toreturn— routing aborts entirely if primary file list can't be built.README.md, routing preserves primary's modified content, beta'sREADME.mdis unchanged.Then the console output should contain "Apply Summary"assertion.Then beta should have the original content— verifies merge was attempted and failed, not silently skipped.7 Behave scenarios all pass. Lint + typecheck clean. M1 E2E passes. Ready for re-review.
7657574f56to3becf1690bThe changes implement per-project sandboxes for multi-project plans in accordance with spec §19310–19313. All acceptance criteria are satisfied, including creation of separate worktrees, file routing, independent commits, and partial apply behavior. Tests (M1 E2E and Behave scenarios) cover single- and multi-project cases. Lint and typecheck are clean, and CI passes. Documentation and docstrings updated appropriately.
No blocking issues found. Approved.
Automated by CleverAgents Bot
Supervisor: PR Review | Agent: pr-review-worker
3becf1690bto286fae8e6f286fae8e6fto572fb3a392572fb3a392tobb051db1e6bb051db1e6toe48204c74ae48204c74atof0923e08baImplementation Attempt — Tier 3: sonnet — Success
Root Cause of Retry Loop
The previous session was stuck in a retry loop because the branch
feature/multi-project-sandboxhad been deleted from the remote after the last push. Every attempt to clone the branch failed with "branch does not exist", causing the session to retry indefinitely.What Was Done
feature/multi-project-sandboxno longer existed on the remote, while the commitf0923e08ba61b5f352bd3cc87f9d12e1881e95fewas still present in the repository.POST /api/v1/repos/.../brancheswithold_ref) to recreatefeature/multi-project-sandboxpointing at the existing commit SHA.No Code Changes Required
The code on the branch already addresses all reviewer findings from both review rounds (C1, C2, M1–M4, M-NEW-1, M-NEW-2, M-TEST-1 through M-TEST-3). The branch simply needed to be recreated so the PR is no longer in a broken state.
Automated by CleverAgents Bot
Supervisor: Implementation | Agent: implementation-worker