fix(FN-4114): normalize invariant requeue state and add changeset

Fusion-Task-Id: FN-4114
Fusion-Task-Lineage: 44fd5718-42dc-44d1-8f5a-97b67dd05d13
This commit is contained in:
Fusion
2026-05-13 04:14:11 -07:00
committed by gsxdsm
parent a5a4fd6e6f
commit e43cdd2158
2 changed files with 23 additions and 1 deletions

View File

@@ -0,0 +1,5 @@
---
"@runfusion/fusion": patch
---
Engine reliability: fn_task_done now rejects completion when the executor session is not running in the task worktree/branch or has zero commits beyond base, and worktree liveness is asserted before session start. Both failure modes route through the existing auto-retry path so the task returns to todo instead of producing a ghost completion.

View File

@@ -2551,6 +2551,8 @@ export class TaskExecutor {
branch: null, branch: null,
sessionFile: null, sessionFile: null,
taskDoneRetryCount: nextRequeueCount, taskDoneRetryCount: nextRequeueCount,
paused: false,
pausedByAgentId: null,
}); });
await this.store.logEntry( await this.store.logEntry(
task.id, task.id,
@@ -2567,6 +2569,8 @@ export class TaskExecutor {
worktree: null, worktree: null,
branch: null, branch: null,
sessionFile: null, sessionFile: null,
paused: false,
pausedByAgentId: null,
}); });
await this.store.logEntry(task.id, `${failureMessage} — moved to in-review for inspection`, undefined, this.currentRunContext); await this.store.logEntry(task.id, `${failureMessage} — moved to in-review for inspection`, undefined, this.currentRunContext);
await this.persistTokenUsage(task.id); await this.persistTokenUsage(task.id);
@@ -4538,6 +4542,11 @@ export class TaskExecutor {
status: "failed", status: "failed",
error: refusalMessage, error: refusalMessage,
taskDoneRetryCount: nextRequeueCount, taskDoneRetryCount: nextRequeueCount,
paused: false,
pausedByAgentId: null,
worktree: null,
branch: null,
sessionFile: null,
}); });
await store.logEntry( await store.logEntry(
taskId, taskId,
@@ -4548,7 +4557,15 @@ export class TaskExecutor {
await store.moveTask(taskId, "todo", { preserveProgress: true }); await store.moveTask(taskId, "todo", { preserveProgress: true });
executorLog.log(`${taskId} failed invariant check — requeued to todo (${nextRequeueCount}/${MAX_TASK_DONE_REQUEUE_RETRIES})`); executorLog.log(`${taskId} failed invariant check — requeued to todo (${nextRequeueCount}/${MAX_TASK_DONE_REQUEUE_RETRIES})`);
} else { } else {
await store.updateTask(taskId, { status: "failed", error: refusalMessage }); await store.updateTask(taskId, {
status: "failed",
error: refusalMessage,
paused: false,
pausedByAgentId: null,
worktree: null,
branch: null,
sessionFile: null,
});
await store.logEntry(taskId, `${refusalMessage} — moved to in-review for inspection`, undefined, this.currentRunContext); await store.logEntry(taskId, `${refusalMessage} — moved to in-review for inspection`, undefined, this.currentRunContext);
await this.persistTokenUsage(taskId); await this.persistTokenUsage(taskId);
await store.moveTask(taskId, "in-review"); await store.moveTask(taskId, "in-review");