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:
gsxdsm
2026-08-09 19:10:45 -07:00
parent 51a5e1c275
commit 351c3b7e28
4 changed files with 94 additions and 1 deletions

View 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.

View File

@@ -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");
});
});

View File

@@ -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:

View File

@@ -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,