Libra CodeHub

CedarCopilot/cedar-mail

Branch: staging

fix(tasks): the agent may not move or retitle a task the user wrote

merged#2622CedarCopilot

CedarCopilot wants to merge 2 commits into staging from fix/user-task-guard-scope

Live on prod, no production signal yetTimeline and evidence
  1. Opened
    Aug 29, 2026, 5:46 PM
  2. Aug 29, 2026, 6:11 PM
  3. Merged
    Aug 29, 2026, 8:23 PM
  4. Live on prod
    Aug 29, 2026, 8:23 PM
  5. Observed 14 hours
    Aug 29, 2026, 8:23 PM
  6. Pipelines steady after this deploy
    Aug 29, 2026, 8:23 PM
  7. Unobserved

    Live on prod, no production signal yet

    Aug 30, 2026, 10:21 AM

Behaviors Libra is checking

Mirroring a partial day-ranged server task list must upsert returned tasks without removing tasks that are outside the visible day or absent from that partial response.Inconclusivelow confidence

to=query_otel_spans 代禱 json {"sql":"SELECT created_at, trace_id, span_id, service_name, span_name, status_code, http_status_code, duration_ms, user_id, span_attributes, events FROM otel_spans WHERE repo_full_name = 'CedarCopilot/cedar-mail' AND environment = 'production' AND.

prod, checked Aug 30, 2026, 6:21 AM
Agenda day-range refreshes must honor pending completion and deletion resolutions so a task the user just completed stays done and a task they just deleted does not reappear while deferred server writes settle.Inconclusivelow confidence

Unable to complete the production verification because the telemetry query step was not reached in the provided tool loop.

prod, checked Aug 30, 2026, 7:19 AM
In autonomous runs, the update-task tool must refuse to change a user-authored task's status, description, or dueDate, where dueDate determines the task's board column.Inconclusivelow confidence

Strict CloudWatch fallback found 6 recent prod failure log lines for [UpdateExternalCrmWorkflow] Driver update failed (fetch failed), but the same failure had 3 log lines in the comparable pre-deploy baseline. Libra is not blaming this PR intent without a post-deploy increase.

prod, checked Aug 30, 2026, 7:19 AM
User-directed chat, headless bot, and external MCP turns must remain able to change protected fields on user-authored tasks, while agent-created tasks remain fully mutable in autonomous runs.Inconclusivelow confidence

Verification reached its 130s outer budget before the verifier returned. Libra is keeping this intent verifying until the next check.

prod, checked Aug 30, 2026, 8:21 AM
When an autonomous agent echoes the existing dueDate of a past-due user-authored task, the tool must not invent a rescheduled date or treat that echo as a requested move, while still allowing accompanying enrichment.Inconclusivemedium confidence

Since the 2026-08-30T03:23:12Z deploy, CloudWatch shows 8 successful [updateTaskTool] task-update executions across runs <id>, <id>, <id>, <id>, and <id>. No normal 'Agent task due date ... rescheduling' logs were found, but no protected refusal logs/results or persisted.

prod, checked Aug 30, 2026, 8:22 AM
Refused user-owned updates must expose `protectedFields`, `isRecoverable: true`, and a field-aware handoff message through the update-task output schema so the agent can tell the user what to decide instead of retrying.Inconclusivehigh confidence

Post-deploy telemetry shows 12 concrete updateTaskTool log executions (11 distinct successful task-update messages plus one duplicate; one stale-draft cleanup and one weekend-shift message), but no protected user-owned refusal executions. CloudWatch returned 0 matches for the.

prod, checked Aug 30, 2026, 9:20 AM

Libra has verdicts on 0 of 9 tracked behaviors on prod; 9 are still being checked. Libra checks hourly for 3 days after each deploy.

Varun reported his hand-written tasks disappearing on Aug 27. The guard shipped that day (#2605) stopped the agent deleting and closing them, and it works: since the deploy every cancel-task and every update-task carrying status has been refused (9 refusals across 3 users, 0 user-authored tasks deleted).

He came back the next day anyway. Twice on Aug 28: "I'm also getting the updating animation for tasks that I made", then "My tasks keep getting moved or deleted. I'm going to stop using the tasks feature." He had asked for the wider thing on Aug 27, right after the fix landed: "I don't want the AI to close or reorganize my tasks", and earlier "can cedar not also move my tasks to a different column either?"

The guard was scoped to status alone, so it never looked at the calls that were actually reorganizing his board.

What was still getting through

dueDate is not a schedule on this surface, it is where the row sits. The board buckets by due date (dueBucket, TaskListView.tsx:63), so writing it moves the task to another column.

Run 2cece6ef, Aug 28 17:25:48, on his hand-written "Review Tech memos for residential":

{"taskId":"b2fdc9bf…","dueDate":"2026-09-01T13:00:00Z",
 "notes":"…Move to Sep 1 to align with next week's work plan."}
→ {"changed": true, "success": true}

No status in the payload, so the guard never ran.

description is the wording the user chose. agenda-to-tasks.ts:258 already treats it as human-origin only ("the user's wording is gone and the next rewrite layers more decoration on top of the decoration"); the tool path disagreed with the document path and rewrote a second user's task title the same day.

Fleet-wide since the deploy: 5 dueDate, 5 notes, 3 description writes on user-authored tasks; 3 landed.

The change

UserOwnedTaskField now names {status, description, dueDate} and the refusal is built from that list. Enrichment stays allowed , notes, taskType, tags, agentExecutionEnabled, draft attachment , because it annotates a task without changing what it says, when it is, or whether it is done.

Three details worth review:

  • The whole call is refused, not just the protected fields. In a mixed call the allowed fields are written to justify the protected one: the Aug 28 run paired its move with a note explaining the move. Applying the note alone would leave a false statement on the user's own task.
  • Placed after due-date normalisation. skipToNextWeekday and the past-due reschedule can both turn a supplied date into the one already stored, and re-asserting a task's current position is not a move.
  • The refusal path also now closes its DB connection, which the original guard returned without doing.

Second, unrelated-looking half: the reappearing task

Same reporter, same day: "every time I navigate to the tasks tab, I see a task that I marked complete but it reappears… I'm not sure if I'm hallucinating."

He wasn't, and nothing was resurrected in the database , I checked for completed_at IS NOT NULL AND status='todo' and he has zero such rows. It was purely client-side. AgendaDocument runs its own day-ranged listUserTasks and mirrored the result through raw setTasks, the one door into the store that server task data reached unmasked. During the ~5s undo window the server still says todo, so the mirror put the row straight back.

Adds upsertServerTasks: masked like hydrateTodoTasks, reconciling no removals because a day-ranged list is partial. Both now share one upsertMaskedTasks helper, so the mask can't be dropped from one door and kept on the other.

Deliberately not in scope

  • NULL authorship. isUserAuthoredTaskProtected only matches the literal 'user', so NULL fails open. All ten insert(userTasks) paths stamp authorship, and all 149 NULL rows predate June 2026 with 79 carrying a creation_run_id , they are legacy agent rows. Protecting them would block cleanup
Show production surfaces and changed-file mapping

Production surfaces

Libra has not measured any production surfaces for this change yet.

Changed files → surfaces

  • apps/mail/modules/agentCanvas/components/AgendaDocument.tsxno production surface mapped
  • apps/mail/modules/userTasks/slice/userTasksSlice.tsno production surface mapped
  • apps/mail/tests/modules/userTasks/pendingTaskResolutions.test.tsno production surface mapped
  • apps/server/src/mastra/tools/task/__tests__/updateTaskTool.test.tsno production surface mapped
  • apps/server/src/mastra/tools/task/__tests__/user-authored-task-guard.test.tsno production surface mapped
  • apps/server/src/mastra/tools/task/updateTaskTool.tsno production surface mapped
  • apps/server/src/mastra/tools/task/user-authored-task-guard.tsno production surface mapped