feat(FN-4068): add branch conflict detection and recovery for stale worktre
Implements branch conflict detection and recovery across the Fusion engine, CLI, and dashboard — surfacing git worktree conflicts when tasks conflict with unrelated branch state, and providing a recovery workflow to resolve them. The executor and worktree pool now integrate typed branch conflict che Fusion-Task-Id: FN-4068
This commit is contained in:
@@ -459,6 +459,20 @@ fn task pause FN-001
|
||||
fn task unpause FN-001
|
||||
```
|
||||
|
||||
### Branch recovery
|
||||
|
||||
```bash
|
||||
fn task branch-recovery FN-001
|
||||
fn task branch-recovery FN-001 --reclaim fusion/fn-001
|
||||
fn task branch-recovery FN-001 --discard fusion/fn-001-2 --yes
|
||||
```
|
||||
|
||||
Use `fn task branch-recovery` when executor branch allocation fails because the canonical task branch is already checked out elsewhere.
|
||||
|
||||
- No flags: inspect canonical + sibling recovery candidates, including tip SHA, attached worktree path, and stranded commits.
|
||||
- `--reclaim <branch>`: point the task back at an existing canonical/sibling branch so the next executor run resumes from that branch.
|
||||
- `--discard <branch> --yes`: explicitly delete a stranded branch/worktree. `--yes` is required for destructive cleanup.
|
||||
|
||||
### Node routing controls
|
||||
|
||||
```bash
|
||||
|
||||
@@ -209,6 +209,7 @@ Override precedence for direct merges is:
|
||||
| `testCommand` | `string` | `undefined` | Merge-time test command (hard gate). When unset, Fusion auto-detects from lockfile. |
|
||||
| `buildCommand` | `string` | `undefined` | Merge-time build command (hard gate). |
|
||||
| `recycleWorktrees` | `boolean` | `false` | Reuse worktrees from a pool for faster startup. |
|
||||
| `executorAllowSiblingBranchRename` | `boolean` | `false` | Escape hatch for legacy branch-collision behavior. When `false` (default), executor branch-name collisions fail loudly, leave the task in `todo` with `status: "failed"`, and surface stranded commits for explicit recovery. When `true`, Fusion restores the old silent sibling-branch rename flow (`fusion/<task-id>-2`, `-3`, …), which is discouraged because it can hide prior work behind suffixed branches. |
|
||||
| `worktreeNaming` | `"random" \| "task-id" \| "task-title"` | `"random"` | Naming mode for new worktree directories. |
|
||||
| `taskPrefix` | `string` | `"FN"` | Prefix used for newly generated task IDs. |
|
||||
| `includeTaskIdInCommit` | `boolean` | `true` | Include task ID as commit scope in generated commits. |
|
||||
|
||||
@@ -24,21 +24,6 @@ The script checks for:
|
||||
|
||||
Treat flagged candidates as recovery leads, not automatic truth: review the surviving task files, logs, and commit history, then file a follow-up recovery task for any confirmed overwrite.
|
||||
|
||||
### Reconciling stale task title/description vs canonical PROMPT.md
|
||||
|
||||
Use the one-shot reconciliation script only when the surviving evidence agrees on a single canonical task identity and the ambiguity is limited to stale metadata fields on that same task row:
|
||||
|
||||
```bash
|
||||
node scripts/reconcile-fn-3909-identity.mjs [--project-root /path/to/project] [--apply]
|
||||
```
|
||||
|
||||
The script is intentionally narrow and idempotent:
|
||||
- dry-run is the default and prints the before/after title + description diff without mutating anything
|
||||
- `--apply` only updates task `FN-3909` through `TaskStore.updateTask(...)` and appends an audit log entry referencing `FN-4194`
|
||||
- the script refuses to run if `PROMPT.md` no longer matches the expected canonical heading, if the stale heartbeat-scope row contents are not present, or if the row is already canonical without the reconciliation marker
|
||||
|
||||
Use this path for the confirmed FN-3909 mismatch (canonical UI-fix prompt/merge history, stale heartbeat-scope title/description). Do **not** use it for allocator-collision or overwrite incidents that may involve multiple tasks or conflicting survivors; run `scripts/audit-task-id-collisions.mjs` first and treat those cases as recovery/postmortem work instead of automatic metadata repair.
|
||||
|
||||
## SQLite write-path lock recovery (FN-4042 / FN-4083)
|
||||
|
||||
- Every disk-backed SQLite connection that Fusion opens for project storage (`fusion.db`), the central registry (`fusion-central.db`), archives (`archive.db`), and worktree hydration explicitly sets `PRAGMA busy_timeout = 5000` and `PRAGMA journal_mode = WAL` at connection open time before write work begins.
|
||||
|
||||
@@ -117,6 +117,14 @@ fn task archive FN-001
|
||||
fn task unarchive FN-001
|
||||
```
|
||||
|
||||
### Lifecycle invariants
|
||||
|
||||
**Paused-state normalization on reopen:** When a task is moved from `in-progress`, `in-review`, or `done` back to `todo` or `triage` (retry/requeue), Fusion clears `task.paused` and `task.pausedByAgentId` to prevent contradictory `todo + paused` or `in-progress + paused` states. A paused task in `todo` is excluded from scheduler dispatch.
|
||||
|
||||
**Paused-state normalization on explicit completion:** When an agent calls `fn_task_done` on a paused task, Fusion clears `task.paused` and `task.pausedByAgentId` regardless of the task's column (`in-progress` or `todo`). `task.paused` prevents new work from starting, but does not block an agent from completing in-flight work and transitioning the task to `done`. The scheduler respects `globalPause` independently.
|
||||
|
||||
**Global pause vs task pause:** `settings.globalPause` gates new scheduler dispatches and is checked by the `fn_task_done` handoff logic. Task-level `task.paused` is a per-task gate that blocks execution start. They are independent — a task can be paused individually even when `globalPause` is `false`, and clearing `task.paused` does not affect `globalPause`.
|
||||
|
||||
### Branch metadata semantics
|
||||
|
||||
Task cards on the board only surface branch metadata when it is non-default/user-meaningful: they hide the conventional auto-generated working branch (`fusion/<task-id>` and suffixed variants) and hide the default merge target (`main`), while still showing custom working branches and non-default merge targets.
|
||||
@@ -135,6 +143,30 @@ Task branch fields are intentionally distinct:
|
||||
|
||||
`PrInfo.baseBranch` is unchanged and continues to represent pull-request target branch metadata.
|
||||
|
||||
### Loud branch-conflict recovery
|
||||
|
||||
When the executor tries to allocate the canonical task branch (`fusion/<task-id>`) and finds that branch already checked out in another live worktree, Fusion now **fails loudly by default** instead of silently forking work onto `fusion/<task-id>-2`, `-3`, and similar siblings.
|
||||
|
||||
Default behavior:
|
||||
- task moves from `in-progress` back to `todo`
|
||||
- task `status` becomes `"failed"`
|
||||
- task keeps recovery context on the canonical branch/worktree metadata
|
||||
- task lifecycle logs and agent logs include the existing tip SHA plus stranded commit subjects
|
||||
|
||||
Recovery is explicit:
|
||||
|
||||
```bash
|
||||
fn task branch-recovery FN-001
|
||||
fn task branch-recovery FN-001 --reclaim fusion/fn-001
|
||||
fn task branch-recovery FN-001 --discard fusion/fn-001-2 --yes
|
||||
```
|
||||
|
||||
- **Inspect** lists the canonical branch and any sibling branches with tip SHA, attached worktree path, and stranded commits.
|
||||
- **Reclaim** updates task metadata so the next executor run resumes from the chosen branch/worktree instead of allocating a fresh sibling.
|
||||
- **Discard** removes a selected stranded branch/worktree only when `--yes` is supplied.
|
||||
|
||||
If you must preserve the old silent suffixing flow for a legacy workflow, set project setting `executorAllowSiblingBranchRename=true`. This is discouraged because it can hide earlier commits behind suffixed sibling branches and make recovery less obvious.
|
||||
|
||||
### Dependency reconciliation guidance
|
||||
|
||||
When a task was created to resolve a temporary failure state in another task (for example, a preserved `in-review/failed` merge condition), its dependency contract may become stale after recovery.
|
||||
|
||||
Reference in New Issue
Block a user