fix(idle): exclude completed feedback history from background work (#16764)

Allow private feedback history and acknowledged uploads to stop blocking idle sleep. Pending, failed, unfamiliar, and inconsistent delivery records remain blocked; preserve all other work and cleanup gates.

Verified with 220 idle-safety tests, workspace typecheck/build, full CI, and Greptile Apex 5/5 on the reviewed head. Local full-suite limitations and isolated rechecks are documented in the PR.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Devin FoleyandPaperclip authored and GitHub committed 2026-10-18 13:56:08 -07:00
1 parent 5dced3c766
commit 1558d9962c
3 files changed
+51 -1

No files matched your search

+8
View File
@@ -411,6 +411,14 @@ Rollback by setting the marker to `0` and restarting. No schema changes are
required. This mechanism does not sleep claimed workspaces or replace a durable
scheduler for their background work.
## Feedback history and idle sleep
Automatic idle checks treat private, unshared feedback as saved data. A sent
feedback export is history only when its completion timestamp exists and its
failure reason is clear. Pending, failed, unknown, and inconsistent exports
still keep the instance awake. These checks do not delete history or change
feedback sharing and retry behavior.
## Resource membership tables
Paperclip stores current-user sidebar membership state in:
@@ -5,6 +5,7 @@ import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest
import {
agentInstructionWorkingCopies, agents, agentApiKeys, agentWakeupRequests, companies, companySecretProposals, createDb,
adapterAuthSessions, environments, environmentLeases, executionWorkspaces,
feedbackExports, feedbackVotes,
heartbeatRuns, issues, issueWatchdogs, issueThreadInteractions, issueQuestionResponseDeliveries,
projects, projectWorkspaces, routines, routineTriggers, toolApplications, toolConnections, workspaceOperations, executionWorkspaceRuntimeLeases, type Db,
} from "@paperclipai/db";
@@ -186,6 +187,42 @@ if (!support.supported) console.warn(`Skipping idle sleep Postgres tests: ${supp
return { companyId, agentId, runId };
}
async function feedbackHistory(status: string, change: Partial<typeof feedbackExports.$inferInsert> = {}) {
const f = await seed();
const [issue] = await db.insert(issues).values({ companyId: f.companyId, title: "Finished fixture", status: "done" }).returning();
const [vote] = await db.insert(feedbackVotes).values({ companyId: f.companyId, issueId: issue.id,
targetType: "issue", targetId: issue.id, authorUserId: "fixture-user", vote: "up" }).returning();
await db.insert(feedbackExports).values({ companyId: f.companyId, feedbackVoteId: vote.id,
issueId: issue.id, authorUserId: "fixture-user", targetType: "issue", targetId: issue.id,
vote: "up", status, targetSummary: {}, ...change });
}
it("permits saved private feedback without sharing or deleting its history", async () => {
await feedbackHistory("local_only");
expect(await read()).toEqual(none);
expect(await db.select({ status: feedbackExports.status }).from(feedbackExports)).toEqual([{ status: "local_only" }]);
});
it("permits an acknowledged feedback upload and preserves its receipt", async () => {
await feedbackHistory("sent", { exportedAt: new Date(now), failureReason: null });
expect(await read()).toEqual(none);
expect(await db.select({ status: feedbackExports.status, exportedAt: feedbackExports.exportedAt }).from(feedbackExports))
.toEqual([{ status: "sent", exportedAt: new Date(now) }]);
});
it.each(["pending", "failed", "unfamiliar"])("keeps %s feedback work awake even with an old completion stamp", async status => {
await feedbackHistory(status, { exportedAt: new Date(now), failureReason: null });
expect(await read()).toEqual(present);
});
it.each([
{ exportedAt: null, failureReason: null },
{ exportedAt: new Date(now), failureReason: "unconfirmed delivery" },
])("keeps inconsistent sent feedback awake: %j", async change => {
await feedbackHistory("sent", change);
expect(await read()).toEqual(present);
});
async function scheduledRoutine(at = now + 600_000) {
const fixture = await seed();
const [routine] = await db.insert(routines).values({ companyId: fixture.companyId,
+6 -1
View File
@@ -227,6 +227,11 @@ const WORK_CHECKS = [
'connection-intent:' || d.interaction_id::text || ':rejected',
'connection-intent:' || d.interaction_id::text || ':expired')
AND (${finishedIdleWake('connection_wake')}) IS NOT TRUE)`,
// Saved private feedback and acknowledged uploads have no delivery job.
// Pending/failed exports and unfamiliar states retain the instance. A sent
// label requires its completion stamp and a cleared failure reason.
`SELECT 1 FROM feedback_exports WHERE (status = 'local_only'
OR (status = 'sent' AND exported_at IS NOT NULL AND failure_reason IS NULL)) IS NOT TRUE`,
// These less common work sources fail closed on any retained state. Their
// terminal-state exceptions can be added with tests for the owning service.
...[
@@ -236,7 +241,7 @@ const WORK_CHECKS = [
"execution_workspace_runtime_leases", "pipeline_automation_executions",
"native_run_finalizations", "company_transfer_runs", "decisions", "decision_effect_executions",
"decision_archive_notification_outbox", "browser_use_sessions", "browser_use_runs",
"browser_use_browsers", "feedback_exports",
"browser_use_browsers",
"company_secret_proposals",
"mcp_oauth_grants", "mcp_mutation_receipts", "mcp_event_subscriptions",
"mcp_event_deliveries", "mcp_attachment_uploads", "dot_agent_bindings",