perf: omit unused descriptions from recovery candidates (#17695)

Keep unused task descriptions out of the recurring candidate read. Preserve the task content, state gates, and full current-task reload before repair.

Verified 86 focused recovery tests, root typecheck, server build, a failing base counterfactual, all 55 CI checks, and fresh Apex 5/5 review with no unresolved comments.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Devin FoleyandPaperclip authored and GitHub committed 2026-10-25 21:50:48 -07:00
1 parent 6bdcb70244
commit 1fbbef45a8
2 files changed
+75 -33

No files matched your search

@@ -1,5 +1,5 @@
import { randomBytes, randomUUID } from "node:crypto";
import { companies, createDb, issueRecoveryActions, issues } from "@paperclipai/db";
import { agents, companies, createDb, issueRecoveryActions, issues } from "@paperclipai/db";
import { createDatabaseResponseCapture, installDatabaseResponseCapture } from "@paperclipai/db/response-capture";
import { eq } from "drizzle-orm";
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
@@ -76,4 +76,42 @@ describe.skipIf(!support.supported)("recovery issue state reads", () => {
}
expect((await db.select().from(issueRecoveryActions).where(eq(issueRecoveryActions.id, action.id)))[0]).toEqual(action);
});
it("keeps a candidate waiting for agent setup without reading its description", async () => {
const [company] = await db.insert(companies).values({
name: "Recovery candidate read",
issuePrefix: `R${randomUUID().slice(0, 7).toUpperCase()}`,
}).returning();
const [agent] = await db.insert(agents).values({
companyId: company.id,
name: "Preparing agent",
lifecycleState: "preparing",
}).returning();
const [issue] = await db.insert(issues).values({
companyId: company.id,
title: "Wait for agent setup",
description: randomBytes(128 * 1024).toString("base64"),
status: "todo",
assigneeAgentId: agent.id,
}).returning();
const enqueueWakeup = vi.fn(async () => {
throw new Error("Setup still owns this task");
});
const service = recoveryService(db, { enqueueWakeup });
const events: Array<Record<string, unknown>> = [];
const now = Date.now();
const capture = createDatabaseResponseCapture({ start: now - 100, end: now + 59_000 }, event => events.push(event));
installDatabaseResponseCapture(db, { agents, issues, issueRecoveryActions }, capture);
try {
expect(await service.reconcileStrandedAssignedIssues()).toMatchObject({ skipped: 1 });
} finally {
capture.flush();
}
expect(Number(events.at(-1)?.completedQueries)).toBeGreaterThan(5);
expect(Number(events.at(-1)?.responseValueBytes)).toBeLessThan(16_384);
expect(enqueueWakeup).not.toHaveBeenCalled();
expect((await db.select().from(issues).where(eq(issues.id, issue.id)))[0]).toEqual(issue);
expect((await db.select().from(agents).where(eq(agents.id, agent.id)))[0]).toEqual(agent);
expect(await db.select().from(issueRecoveryActions).where(eq(issueRecoveryActions.companyId, company.id))).toEqual([]);
});
});
+36 -32
View File
@@ -864,7 +864,7 @@ function formatIssueLinksForComment(
}
function isStrandedIssueRecoveryIssue(
issue: Pick<typeof issues.$inferSelect, "originKind">,
issue: Pick<RecoveryIssue, "originKind">,
) {
return isStrandedIssueRecoveryOriginKind(issue.originKind);
}
@@ -936,6 +936,10 @@ function isRepeatedProductiveContinuationRecovery(
);
}
type RecoveryIssue = Omit<typeof issues.$inferSelect, "description">;
const { description: _unusedIssueDescription, ...recoveryIssueFields } = getTableColumns(issues);
export function recoveryService(
db: Db,
deps: {
@@ -1251,7 +1255,7 @@ export function recoveryService(
* Keep it separate from the broader monitor/delegated/legacy wait predicate.
*/
async function hasCurrentNativePassiveWait(
issue: typeof issues.$inferSelect,
issue: RecoveryIssue,
latestRun: LatestIssueRun,
): Promise<boolean> {
if (
@@ -1461,7 +1465,7 @@ export function recoveryService(
}
async function hasPersistedDurableWaitPath(
issue: typeof issues.$inferSelect,
issue: RecoveryIssue,
latestRun: LatestIssueRun,
) {
if (issue.monitorNextCheckAt) return true;
@@ -1526,7 +1530,7 @@ export function recoveryService(
}
async function nativeBlockedUnblockAction(
issue: typeof issues.$inferSelect,
issue: RecoveryIssue,
latestRun: LatestIssueRun,
database: Db = db,
) {
@@ -1597,7 +1601,7 @@ export function recoveryService(
}
async function repairNativeBlockedWait(
issue: typeof issues.$inferSelect,
issue: RecoveryIssue,
latestRun: NonNullable<LatestIssueRun>,
candidate: NonNullable<
Awaited<ReturnType<typeof nativeBlockedUnblockAction>>
@@ -1801,7 +1805,7 @@ export function recoveryService(
}
async function wasTodoHandedBackDuringOrAfterLatestRun(
issue: typeof issues.$inferSelect,
issue: RecoveryIssue,
latestRun: LatestIssueRun,
) {
if (issue.status !== "todo" || latestRun?.status !== "succeeded")
@@ -2133,7 +2137,7 @@ export function recoveryService(
}
async function enqueueInitialAssignedTodoDispatch(
issue: typeof issues.$inferSelect,
issue: RecoveryIssue,
agentId: string,
) {
return deps.enqueueWakeup(agentId, {
@@ -2166,7 +2170,7 @@ export function recoveryService(
// contract is that nothing runs until the user types. Until a user-authored
// comment exists on it, the issue is intentionally idle rather than stranded.
async function isOnboardingFirstTaskAwaitingUser(
issue: typeof issues.$inferSelect,
issue: RecoveryIssue,
) {
if (issue.originKind !== ONBOARDING_FIRST_TASK_ORIGIN_KIND) return false;
const [result] = await db.execute<{ awaiting: boolean }>(sql`SELECT
@@ -2175,7 +2179,7 @@ export function recoveryService(
}
async function isInvocationBudgetBlocked(
issue: typeof issues.$inferSelect,
issue: RecoveryIssue,
agentId: string,
) {
const budgetBlock = await budgets.getInvocationBlock(
@@ -2409,12 +2413,12 @@ export function recoveryService(
}
}
function isStrandedIssueRecoveryIssue(issue: typeof issues.$inferSelect) {
function isStrandedIssueRecoveryIssue(issue: RecoveryIssue) {
return issue.originKind === STRANDED_ISSUE_RECOVERY_ORIGIN_KIND;
}
async function buildNestedStrandedRecoveryLine(
issue: typeof issues.$inferSelect,
issue: RecoveryIssue,
prefix: string,
) {
const sourceIssueId = readNonEmptyString(issue.originId);
@@ -2445,7 +2449,7 @@ export function recoveryService(
}
function resolveStrandedRecoveryRouting(input: {
issue: typeof issues.$inferSelect;
issue: RecoveryIssue;
latestRun: LatestIssueRun;
}) {
const originalAgentId =
@@ -2468,7 +2472,7 @@ export function recoveryService(
}
function strandedRecoveryActionFingerprint(input: {
issue: typeof issues.$inferSelect;
issue: RecoveryIssue;
recoveryCause: StrandedRecoveryCause;
latestRun: LatestIssueRun;
}) {
@@ -2514,7 +2518,7 @@ export function recoveryService(
}
function buildStrandedRecoveryActionEvidence(input: {
issue: typeof issues.$inferSelect;
issue: RecoveryIssue;
latestRun: LatestIssueRun;
previousStatus: StrandedPreviousStatus;
recoveryCause: StrandedRecoveryCause;
@@ -2549,7 +2553,7 @@ export function recoveryService(
}
async function ensureSourceScopedStrandedRecoveryAction(input: {
issue: typeof issues.$inferSelect;
issue: RecoveryIssue;
latestRun: LatestIssueRun;
previousStatus: StrandedPreviousStatus;
recoveryCause?: StrandedRecoveryCause;
@@ -2680,7 +2684,7 @@ export function recoveryService(
}
async function ensureProviderQuotaWaitRecoveryMonitor(input: {
issue: typeof issues.$inferSelect;
issue: RecoveryIssue;
latestRun: LatestIssueRun;
actionId: string;
agentId: string;
@@ -2780,7 +2784,7 @@ export function recoveryService(
}
function buildRecoveryIssueInPlaceEscalationComment(input: {
issue: typeof issues.$inferSelect;
issue: RecoveryIssue;
previousStatus: StrandedPreviousStatus;
latestRun: LatestIssueRun;
prefix: string;
@@ -2815,7 +2819,7 @@ export function recoveryService(
}
async function escalateStrandedRecoveryIssueInPlace(input: {
issue: typeof issues.$inferSelect;
issue: RecoveryIssue;
previousStatus: StrandedPreviousStatus;
latestRun: LatestIssueRun;
}, transaction?: {
@@ -2954,7 +2958,7 @@ export function recoveryService(
);
}
async function openChildIssues(issue: typeof issues.$inferSelect) {
async function openChildIssues(issue: RecoveryIssue) {
return db
.select({ id: issues.id, identifier: issues.identifier })
.from(issues)
@@ -2969,7 +2973,7 @@ export function recoveryService(
}
async function healthyOpenChildIssues(
issue: Pick<typeof issues.$inferSelect, "id" | "companyId" | "projectWorkspaceId">,
issue: Pick<RecoveryIssue, "id" | "companyId" | "projectWorkspaceId">,
sameWorkspaceOnly = false,
) {
if (sameWorkspaceOnly && !issue.projectWorkspaceId) return [];
@@ -3001,7 +3005,7 @@ export function recoveryService(
}
async function resolveContinuationWaitingOnReview(
issue: typeof issues.$inferSelect,
issue: RecoveryIssue,
) {
const [existingBlockers, openChildren] = await Promise.all([
existingUnresolvedBlockerIssues(issue.companyId, issue.id),
@@ -3102,7 +3106,7 @@ export function recoveryService(
}
async function resolveDispositionRepairActionAsCovered(
issue: typeof issues.$inferSelect,
issue: RecoveryIssue,
reason: string,
) {
const active = await recoveryActionsSvc.getActiveForIssue(
@@ -3136,7 +3140,7 @@ export function recoveryService(
}
async function ensureDispositionRepairAction(input: {
issue: typeof issues.$inferSelect;
issue: RecoveryIssue;
latestRun: LatestIssueRun;
fingerprint: string;
attemptCount: number;
@@ -3214,7 +3218,7 @@ export function recoveryService(
}
async function scheduleDispositionRepairAttempt(input: {
issue: typeof issues.$inferSelect;
issue: RecoveryIssue;
latestRun: LatestIssueRun;
action: Awaited<ReturnType<typeof ensureDispositionRepairAction>>;
fingerprint: string;
@@ -3651,7 +3655,7 @@ export function recoveryService(
}
async function escalateDispositionRepair(input: {
issue: typeof issues.$inferSelect;
issue: RecoveryIssue;
latestRun: LatestIssueRun;
fingerprint: string;
attemptCount: number;
@@ -3796,7 +3800,7 @@ export function recoveryService(
}
async function decidePersistedLegacyContinuation(
issue: typeof issues.$inferSelect,
issue: RecoveryIssue,
runId: string,
state: Awaited<ReturnType<typeof collectDispositionRepairSourceState>>,
episode: LegacyDispositionEpisode,
@@ -3876,7 +3880,7 @@ export function recoveryService(
}
async function reconcileDispositionRepair(
issue: Pick<typeof issues.$inferSelect, "id" | "companyId">,
issue: Pick<RecoveryIssue, "id" | "companyId">,
latestRun: LatestIssueRun,
options: { historicalAttemptCount?: number; legacyEpisode?: LegacyDispositionEpisode } = {},
): Promise<"queued" | "escalated" | "covered" | "skipped"> {
@@ -4023,7 +4027,7 @@ export function recoveryService(
}
async function escalateStrandedAssignedIssue(input: {
issue: typeof issues.$inferSelect;
issue: RecoveryIssue;
previousStatus: StrandedPreviousStatus;
latestRun: LatestIssueRun;
comment?: string;
@@ -4390,7 +4394,7 @@ export function recoveryService(
}
async function scheduleProviderQuotaRecoveryMonitor(input: {
issue: typeof issues.$inferSelect;
issue: RecoveryIssue;
latestRun: NonNullable<LatestIssueRun>;
classification: Extract<
NonNullable<AdapterFailureRecoveryClassification>,
@@ -4473,7 +4477,7 @@ export function recoveryService(
}
function getAdapterFailureRecoveryTargetAgentId(
issue: typeof issues.$inferSelect,
issue: RecoveryIssue,
) {
if (issue.status !== "in_review") return issue.assigneeAgentId;
@@ -4488,7 +4492,7 @@ export function recoveryService(
}
function hasPendingProviderQuotaRecoveryMonitor(
issue: typeof issues.$inferSelect,
issue: RecoveryIssue,
latestRun: LatestIssueRun,
now: Date,
) {
@@ -4510,7 +4514,7 @@ export function recoveryService(
issueCreatedAtGte?: Date | null;
}) {
const candidates = await db
.select()
.select(recoveryIssueFields)
.from(issues)
.where(
and(