fix(triage): prevent orphaned deps when splitting tasks + detect worktree drift
Root cause: during a triage split the AI could set a child task's `dependencies` to the parent id. The parent is hard-deleted after the split, and the scheduler's dep check treats a missing id as unmet — permanently blocking the dependent. This stranded FN-2164 behind the deleted FN-2163. - core/store.deleteTask: refuse to delete when any live task still has the id in its `dependencies` array. Throws TaskHasDependentsError listing dependents so callers can rewrite or recover. Covers the triage-split path and any future caller. - engine/triage task_create: validate each proposed dependency before creating a child — reject the parent id, reject unknown task ids, allow siblings created earlier in the same split or pre-existing tasks. - engine/triage split cleanup: wrap the parent deleteTask in try/catch that keeps the parent alive (safer than stranding dependents) and logs the reason. - engine/triage prompts: both the mandatory-split and proactive-split prompts now explicitly state that subtask deps must never reference the parent. - dashboard/routes /subtasks/create-tasks: reject parent-id deps, drop unknown deps with an audit log entry, surface parentTaskCloseError + droppedDependencies in the response instead of silently swallowing them. - engine/executor: on execute entry, detect the drift state (in-progress task with no worktree) and emit a loud log + task log entry; the existing fresh-worktree path then recovers. Prevents silent "operating without a worktree" behavior that we saw on FN-2152. Tests: core: 2907/2907 pass (+5 new, incl. deleteTask guard regression) engine: 2554/2554 pass (+17 new, incl. task_create dep validation) dashboard: 9064/9064 pass (+2 new for /subtasks/create-tasks). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -146,6 +146,7 @@ CREATE TABLE IF NOT EXISTS tasks (
|
||||
mergeRetries INTEGER,
|
||||
workflowStepRetries INTEGER,
|
||||
recoveryRetryCount INTEGER,
|
||||
taskDoneRetryCount INTEGER DEFAULT 0,
|
||||
nextRecoveryAt TEXT,
|
||||
error TEXT,
|
||||
summary TEXT,
|
||||
@@ -1623,6 +1624,15 @@ export class Database {
|
||||
});
|
||||
}
|
||||
|
||||
if (version < 41) {
|
||||
// Tracks self-healing auto-requeues of tasks that failed because the agent
|
||||
// exited without calling task_done with partial step progress. Bounded so
|
||||
// a persistently-broken task cannot loop forever.
|
||||
this.applyMigration(41, () => {
|
||||
this.addColumnIfMissing("tasks", "taskDoneRetryCount", "INTEGER DEFAULT 0");
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user