fix(executor): restore two fixes dropped by the U4 executor peel
PR #3317 rewrote executor.ts from a pre-change base. A refactor rebased off a
stale base does not conflict — it deletes. An audit of every commit touching
executor.ts before the peel found two landed fixes silently removed:
- FN-8850 completion recommendations: the `fn_task_done` VALIDATOR survived the
peel but the engine-appended prompt section asking the executor to produce
recommendations did not. Nothing failed — recommendations were still accepted,
no test covered the prompt wiring, and capture simply stopped. Restored
verbatim in executor/system-prompt.ts, next to the validator it pairs with.
- The WorktreeBaseRefreshError guard (a06a4988d9): without it a pre-session
checkout refusal falls through to the generic terminal sink and parks the task
`failed`, which is the path that produced 99 parks and 47 operator alerts over
2026-08-01..09. Restored in executor/run-implementation.ts as a wait.
Everything else from that window verified intact: external checkout routing
(#3398/#3400/#3401), FN-8864/FN-8868 agent activity telemetry, FN-8863
remediation holds, FN-8841 CLOSE_NO_OP, FN-8870 approval mail, FN-8910 held-task
remediation — line-level diffs looked missing only because the peel rewrote
`this.` to `deps.`.
Adds executor-prompt-completion-recommendations.test.ts to pin the prompt
wiring, since the absence of coverage is what let it disappear silently.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
7
.changeset/restore-executor-peel-regressions.md
Normal file
7
.changeset/restore-executor-peel-regressions.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Restore completion-recommendation capture and the worktree base-refresh guard dropped by a refactor.
|
||||
category: fix
|
||||
dev: PR #3317 (U4 executor peel) rewrote executor.ts from a pre-change base, silently deleting two landed fixes. FN-8850's `getCompletionRecommendationGuidance` and its call site are restored in `executor/system-prompt.ts` (the `fn_task_done` validator survived, so recommendations were validated but never requested). The `WorktreeBaseRefreshError` guard is restored in `executor/run-implementation.ts` so a pre-session checkout refusal is left queued instead of terminally parking the task. New `executor-prompt-completion-recommendations.test.ts` pins the prompt wiring that had no coverage.
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
FNXC:TaskRecommendations 2026-08-10-01:15 (the producer must stay wired to the validator — regression):
|
||||
|
||||
FN-8850 added BOTH a `fn_task_done` validator for completion recommendations AND the engine-appended prompt
|
||||
section that asks the executor to produce them. The U4 executor peel (#3317) rewrote `executor.ts` from a
|
||||
pre-FN-8850 base and dropped the prompt half while keeping the validator. Nothing failed: `fn_task_done` kept
|
||||
accepting `recommendations`, no test covered the prompt wiring, and recommendation capture simply stopped.
|
||||
|
||||
A refactor rebased off a stale base does not conflict — it deletes. These assertions pin the wiring so the
|
||||
producer cannot be removed while the validator that consumes it stays in place.
|
||||
*/
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { getExecutorSystemPrompt } from "../executor/system-prompt.js";
|
||||
|
||||
const prompt = (settings: Record<string, unknown> = {}) => getExecutorSystemPrompt(settings as never);
|
||||
|
||||
describe("executor prompt: completion recommendations", () => {
|
||||
it("asks the executor to produce recommendations at the accepted completion checkpoint", () => {
|
||||
const text = prompt();
|
||||
expect(text).toContain("## Completion recommendations");
|
||||
expect(text).toContain("recommendations: []");
|
||||
});
|
||||
|
||||
it("states the cap from settings so the prompt matches what the validator accepts", () => {
|
||||
// A prompt promising a different maximum than the validator enforces produces rejected completions.
|
||||
expect(prompt({ maxRecommendationsPerTask: 5 })).toContain("at most 5");
|
||||
// Default cap when unset.
|
||||
expect(prompt()).toContain("at most 3");
|
||||
});
|
||||
|
||||
it("tells the executor to send nothing when capture is disabled", () => {
|
||||
const text = prompt({ maxRecommendationsPerTask: 0 });
|
||||
expect(text).toContain("Recommendation capture is disabled for this project");
|
||||
// A zero cap must not invite writes the store would reject.
|
||||
expect(text).not.toContain("at most 0");
|
||||
});
|
||||
|
||||
});
|
||||
@@ -188,7 +188,7 @@ import { resolveDedicatedPlannerColumnsForTask } from "../planner-lane-resolutio
|
||||
import { mergeEffectiveSettings } from "../project/effective-settings.js";
|
||||
import { buildStepFailureMessage, emitProactiveStatus, sanitizeFailureReason } from "../project/proactive-status.js";
|
||||
import { createRunAuditor, generateSyntheticRunId, type EngineRunContext } from "../util/run-audit.js";
|
||||
import { acquireTaskWorktree } from "../worktree/worktree-acquisition.js";
|
||||
import { acquireTaskWorktree, WorktreeBaseRefreshError } from "../worktree/worktree-acquisition.js";
|
||||
import { resolveWorktreesDir } from "../worktree/worktree-paths.js";
|
||||
import {
|
||||
RemovalReason,
|
||||
@@ -2884,6 +2884,26 @@ export async function runImplementation(
|
||||
// Dependency added mid-execution — discard worktree and move to triage
|
||||
deps.depAborted.delete(task.id);
|
||||
await deps.handleDepAbortCleanup(task.id, worktreePath);
|
||||
} else if (err instanceof WorktreeBaseRefreshError) {
|
||||
/*
|
||||
FNXC:WorktreeBaseRefresh 2026-08-10-01:15:
|
||||
Classified FIRST among error types, and re-applied after the U4 executor peel (#3317) rewrote
|
||||
executor.ts from a pre-change base and dropped it. Acquisition throws this BEFORE any session starts,
|
||||
so the generic sink below would park the task `failed` and page the operator for a pre-session
|
||||
checkout state — that path parked 99 tasks and produced 47 operator alerts over 2026-08-01..09.
|
||||
Post-fix only an UNPROVEN tree (failed compensation) still throws, and a later acquisition can repair
|
||||
that once git state changes, so it stays a wait: leave the row cleanly dispatchable and let ordinary
|
||||
scheduling retry it rather than terminalizing recoverable work.
|
||||
*/
|
||||
executorLog.warn(`${task.id}: worktree base refresh blocked execution (${err.refresh.kind}) — leaving the task queued for re-dispatch (not a failure)`);
|
||||
await deps.store.logEntry(
|
||||
task.id,
|
||||
`Worktree base refresh blocked execution (${err.refresh.kind}) — task left queued for a later clean acquisition`,
|
||||
err.refresh.detail,
|
||||
deps.getRunContextFor(task.id),
|
||||
).catch(() => undefined);
|
||||
await deps.persistTokenUsage(task.id);
|
||||
return;
|
||||
} else if (isInvalidAssistantContinuationErrorMessage(errorMessage)) {
|
||||
/*
|
||||
FNXC:PostDoneContinuation 2026-07-16-11:57:
|
||||
|
||||
@@ -262,6 +262,26 @@ policy rather than malfunction. It is appended last so it wins over the base tex
|
||||
applies to a custom operator prompt too (an operator who overrode the prompt still gets a
|
||||
truthful statement of what this session may do).
|
||||
*/
|
||||
function getCompletionRecommendationGuidance(maximum: number): string {
|
||||
/*
|
||||
FNXC:TaskRecommendations 2026-08-09-04:06:
|
||||
Engine-appended guidance preserves the accepted-completion recommendation contract even when an
|
||||
operator customizes the executor prompt. A disabled cap must not invite unavailable writes.
|
||||
|
||||
FNXC:TaskRecommendations 2026-08-10-01:15:
|
||||
Restored verbatim after the U4 executor peel (#3317) rewrote executor.ts from a pre-FN-8850 base and
|
||||
dropped both this function and its call site, leaving the validator wired with no producer.
|
||||
*/
|
||||
if (maximum === 0) {
|
||||
return `## Completion recommendations
|
||||
|
||||
Recommendation capture is disabled for this project (maxRecommendationsPerTask is 0). Ignore any earlier generic recommendation guidance: do not send recommendations, including \`recommendations: []\`; use an honest summary or task log for non-blocking context, and do not fabricate a finding.`;
|
||||
}
|
||||
return `## Completion recommendations
|
||||
|
||||
At the final accepted \`fn_task_done(outcome="completed")\` checkpoint, evaluate optional, non-blocking work discovered outside this task. Send at most ${maximum} task-ready recommendations, each with a stable unique \`id\`, \`title\`, \`description\`, and \`category\`, or explicitly send \`recommendations: []\` when none genuinely qualify. Example populated payload: \`recommendations: [{ id: "follow-up-export", title: "Add task export", description: "Provide a CSV export for completed tasks.", category: "feature" }]\`. Do not fabricate filler or include required current-task work, blockers, secrets, executable commands, reasoning, or duplicate ids. Recommendations are only for completed outcomes; never send them with \`outcome="blocked"\`. Use immediate task creation/delegation only for an explicit task requirement, necessary dependency coordination, or operator direction.`;
|
||||
}
|
||||
|
||||
function getWithheldTaskCreationGuidance(taskCreateWithheld: boolean, delegateWithheld: boolean): string {
|
||||
if (!taskCreateWithheld && !delegateWithheld) return "";
|
||||
const withheld = [
|
||||
@@ -286,9 +306,17 @@ export function getExecutorSystemPrompt(
|
||||
): string {
|
||||
const customPrompt = resolveAgentPrompt("executor", settings.agentPrompts);
|
||||
const basePrompt = customPrompt || EXECUTOR_SYSTEM_PROMPT;
|
||||
/*
|
||||
FNXC:TaskRecommendations 2026-08-10-01:15:
|
||||
Re-applied after the U4 executor peel (#3317) dropped it: `fn_task_done` kept VALIDATING recommendations
|
||||
while nothing asked the executor to produce any, so capture silently stopped. Keep the guidance adjacent to
|
||||
the validator it pairs with — the two must be added or removed together.
|
||||
*/
|
||||
const maximumRecommendations = settings.maxRecommendationsPerTask ?? 3;
|
||||
const sections = [
|
||||
basePrompt,
|
||||
isResearchToolSurfaceEnabled(settings) ? getResearchGuidanceForSurface("executor") : "",
|
||||
getCompletionRecommendationGuidance(maximumRecommendations),
|
||||
getWithheldTaskCreationGuidance(
|
||||
toolAvailability?.taskCreateWithheld === true,
|
||||
toolAvailability?.delegateWithheld === true,
|
||||
|
||||
Reference in New Issue
Block a user