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:
5
.changeset/fn-4114-prevent-ghost-completions.md
Normal file
5
.changeset/fn-4114-prevent-ghost-completions.md
Normal 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.
|
||||||
@@ -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");
|
||||||
|
|||||||
Reference in New Issue
Block a user