CedarCopilot wants to merge 1 commit into staging from fix/task-cleanup-draft-task-output-fallback
Libra ran this behavior against the change and confirmed the check detects when it breaks.
Changed code: tasks.ts.
Live on prod, no production signal yet
Verification reached its budget before a verdict.
Unable to complete the verification because the telemetry query was rejected by the read-only query tool before returning data.
Verification reached its 130s outer budget before the verifier returned. Libra is keeping this intent verifying until the next check.
Since the 2026-09-18T03:41:02Z deploy, CloudWatch shows 383 cleanupDraftForTask invocations, 182 draft-pointer selections, and 182 successful draft deletions across 14 worker streams. Every observed draft selection was followed by a successful deletion. The 55 tied failures.
Verification reached its 130s outer budget before the verifier returned. Libra is keeping this intent verifying until the next check.
Verifier did not provide positive runtime evidence tied to this intent, so Libra is keeping it inconclusive. Original assessment: Since deployment, CloudWatch shows 354 explicit [cleanupDraftForTask] Invoked for task logs across prod: 275 in worker-service and 79 in.
internal_only
internal_only
internal_only
internal_only
internal_only
internal_only
Libra measured 2 of 8 production surfaces on prod. 2 surfaces had under 20 requests, so Libra has not judged them; 4 surfaces had no requests at all. Libra has 2 low-confidence degraded verdicts it is still confirming. Libra checks hourly for 3 days after each deploy.
When a task representing an outstanding email draft is marked done with cleanupDraft: true (the user fulfilled the obligation another way, e.g. sent an equivalent email manually, so the now-stale Cedar-generated Gmail draft should be deleted), cleanup silently no-ops for a large class of tasks and the real Gmail draft is never deleted.
cleanupDraftForTask() (apps/server/src/services/user-tasks/tasks.ts) and its gating check in updateTaskTool.ts's update-task tool both read draftId/emailHeaderMessageId/threadId , and gate entirely , on the deprecated task_action_data column (taskActionData?.channel === 'email'). Since the Aug-2026 migration, task_output is the canonical location for a produced task's draft pointer and task_action_data is null on the common/default case (see apps/server/src/services/user-tasks/output-predicates.ts, which already reads via COALESCE(task_output->>'draftId', task_action_data->>'draftId') for exactly this reason). cleanupDraftForTask/updateTaskTool.ts never got updated to that pattern, so:
deleteDraft() is never called for a task whose pointer lives in task_outputupdate-task still reports { success: true }updateTaskTool.ts, the gate also fed the "does this write anything real?" no-op check, so a cleanupDraft: true call on an already-done task (nothing else changing) was short-circuited before cleanupDraftForTask was ever invokedConfirmed on a real production task (bf7bd732-46db-4654-97b2-3441965add9f, Zach Moskow / ConcentrateAI): task_action_data IS NULL, task_output = {"kind":"email"}, and a real Gmail draft (r4440516407368735025) created 5 minutes before the task was marked done is still sitting undeleted.
cleanupDraftForTask(): read draftId/threadId/emailHeaderMessageId with the same task_output-first, task_action_data-fallback pattern output-predicates.ts already uses for SQL reads, instead of task_action_data exclusively.updateTaskTool.ts's cleanupDraft gate: same fallback, so the no-op short-circuit can no longer swallow the call before cleanup runs. Both axes (taskActionData + taskOutput) are written back in sync via the existing producedOutput helper, matching the paired-write convention clearTaskDraftPointer already follows.[cleanupDraftForTask] Invoked for task … log at entry, so a run's logs always show whether cleanup was ever called, independent of outcome."delete" cedar.draft.action value, emitted via a new recordDraftDeleted() in services/analytics/draft-analytics.ts from the successful-deletion path, following the exact same cedar.draft.* span/attribute pattern already used for "create"/"send".cleanup-draft-pointer.test.ts (draft pointer living only in task_output, emailHeaderMessageId-only, task_output-over-stale-task_action_data precedence, non-email task_output.kind, and the new recordDraftDeleted emission) and in updateTaskTool.test.ts (a cleanupDraft: true call on an already-done task whose pointer lives only in task_output still triggers cleanup instead of being swallowed by the no-op guard).pnpm --filter @zero/server run types , cleanpnpm --filter @zero/server exec vitest run src/services/user-tasks/__tests__/cleanup-draft-pointer.test.ts src/mastra/tools/task/__tests__/updateTaskTool.test.ts , 73/73 passingpnpm deps:check , no dependency violations| Surface | Requests | Errors | p95 | Users | Verdict |
|---|---|---|---|---|---|
| model_chunk Next Steps and Tasks Manager | 136 → 47 | 0 → 0 (0%) | 1 ms → 7 ms | 0 | Working 47 requests since the deploy with 0 errors (0 errors in the 136-request baseline before it). p95 7 ms, was 1 ms. |
| model_step Next Steps and Tasks Manager | 67 → 27 | 0 → 0 (0%) | 32078 ms → 29059 ms | 0 | Working 27 requests since the deploy with 0 errors (0 errors in the 67-request baseline before it). p95 29059 ms, was 32078 ms. |
| invoke_agent Next Steps and Tasks Manager | 33 → 15 | 0 → 0 (0%) | 65219 ms → 57461 ms | 0 | Insufficient traffic 15 requests, under the 20 Libra needs |
| /api/trpc/userTasks.listUserTasks | 6 → 3 | 0 → 0 (0%) | 6248 ms → 724 ms | 0 | Insufficient traffic 3 requests, under the 20 Libra needs |
| /api/trpc/userTasks.getTaskById | 0 → 3 | 0 → 0 (0%) | not measured → 101 ms | 0 | Insufficient traffic 3 requests, under the 20 Libra needs |
| execute_tool list-tasks | 2 → 2 | 0 → 0 (0%) | 63 ms → 292 ms | 0 | Insufficient traffic 2 requests, under the 20 Libra needs |
| POST /api/trpc/userTasks.listUserTasks | 4 → 2 | 0 → 0 (0%) | 6248 ms → 724 ms | 0 | Insufficient traffic 2 requests, under the 20 Libra needs |
| cedar.analytics.draft.delete | 0 → 0 | 0 → 0 (0%) | not measured | 0 | No traffic No requests recorded since this deploy. |
| cedar.analytics.draft.delete | 0 → 0 | 0 → 0 (0%) | not measured | 0 | No traffic No requests recorded since this deploy. |
| /api/trpc/userTasks.listUserTasks | 0 → 0 | 0 → 0 (0%) | not measured | 0 | No traffic No requests recorded since this deploy. |
| POST /api/trpc/userTasks.listUserTasks | 0 → 0 | 0 → 0 (0%) | not measured | 0 | No traffic No requests recorded since this deploy. |
| /api/trpc/userTasks.getTaskById | 0 → 0 | 0 → 0 (0%) | not measured | 0 | No traffic No requests recorded since this deploy. |
| model_chunk Next Steps and Tasks Manager | 16 → 0 | 0 → 0 (0%) | 1 ms → not measured | 0 | No traffic No requests recorded since this deploy. |
| model_step Next Steps and Tasks Manager | 7 → 0 | 0 → 0 (0%) | 18588 ms → not measured | 0 | No traffic No requests recorded since this deploy. |
| invoke_agent Next Steps and Tasks Manager | 1 → 0 | 0 → 0 (0%) | 24843 ms → not measured | 0 | No traffic No requests recorded since this deploy. |
| execute_tool list-tasks | 1 → 0 | 0 → 0 (0%) | 459 ms → not measured | 0 | No traffic No requests recorded since this deploy. |