Libra CodeHub

CedarCopilot/cedar-mail

Branch: staging

fix(tasks): don't fire the duplicate-awareness gate on chat surfaces

merged#2542CedarCopilot

CedarCopilot wants to merge 1 commit into staging from fix/task-dedup-gate-chat

Live on prod, 1 of 2 behaviors degradedTimeline and evidence
  1. Opened
    Aug 21, 2026, 3:04 PM
  2. Aug 22, 2026, 9:06 AM
  3. Merged
    Aug 22, 2026, 9:17 AM
  4. Live on prod
    Aug 22, 2026, 9:17 AM
  5. Observed 3 days
    Aug 22, 2026, 9:17 AM
  6. Pipelines steady after this deploy
    Aug 22, 2026, 9:17 AM
  7. Mixed signals

    Live on prod, 1 of 2 behaviors degraded

    Aug 25, 2026, 8:54 AM

Behaviors Libra is checking

Task creation from non-chat executions using the noLoggingRunId sentinel bypasses the duplicate-awareness gate so it cannot become permanently blocked without tool-call logging.Degradedmedium confidence

Strict CloudWatch fallback found 27 prod failure log lines for Failed to create execution for task (Task already claimed or no longer eligible) in the last 24h while checking createtasktool, nologgingrunid, duplicateawarenessgate, approval-flag-guard.test.

prod, checked Aug 25, 2026, 8:54 AM
Task creation requested from any chat surface, including in-app chat and Slack/iMessage/SMS/WhatsApp bots, creates the task even when the conversation already has open tasks instead of being blocked for duplicate awareness.Not checked
prod, checked Aug 24, 2026, 2:00 PM

Libra has verdicts on 1 of 2 tracked behaviors on prod; 1 is still being checked. Libra checks hourly for 3 days after each deploy.

Problem

On a Slack DM, the chat agent repeatedly failed to create a task, reporting a "duplicate-check gate ... isn't letting the new task through even on retry." The deal (Soren Transport) already had 3 open tasks.

Root cause: the duplicate-awareness gate (§3.0) in createTaskTool is guarded by if (runId && …). It's designed to fire at most once and let the agent through on retry , but that bypass only works if the retry "breadcrumb" gets logged. Every chat surface (in-app chat + the Slack/iMessage/SMS/WhatsApp bots) runs with runId = 'noLoggingRunId', a sentinel that:

  1. is a truthy string, so it triggers the gate, and
  2. is the exact value logToolCall short-circuits on (tool-calls.ts:98), so it disables all tool-call logging.

With logging off, the gate's alreadyLooked query (a prior list-tasks row, or its own duplicateAwarenessGate: true log) can never find anything. So the gate re-fires on every attempt , neither retrying nor calling list-tasks first can clear it. Task creation is permanently impossible on chat for any deal that already has an open task. This affects in-app chat too, not just Slack.

The gate's own comment claimed "interactive chat is unaffected" , that assumption was that chat has no runId, but chat actually passes the truthy sentinel.

Fix

Exclude the sentinel from the guard:

if (runId && runId !== 'noLoggingRunId' && status !== 'recommended') {

This is the same runId !== 'noLoggingRunId' idiom already used across sibling tools (updateTaskTool, the calendar tools, writeDocumentTool). Now:

  • Chat surfaces skip the gate entirely , if the user asks the agent to create a task, it always creates it (a duplicate is acceptable, per the chat contract).
  • Real logged automation/event runs are unchanged , the gate still protects them, and there its list-tasks/breadcrumb retry mechanism actually works (each event execution logs its calls and handles one conversation).

Also rewrote the stale comment to describe the true invariant.

Tests

Added a regression case to createTaskTool.duplicate-gate.test.ts: with runId: 'noLoggingRunId' and an open task present, the task is created and the gate does not fire.

Verification

  • pnpm --filter @zero/server run types → clean on both changed files
  • pnpm --filter @zero/server exec vitest run …/createTaskTool.duplicate-gate.test.ts → 8/8 pass
  • pnpm deps:check → no violations

Follow-up (not in this PR)

The 'noLoggingRunId' literal is now compared in ~8 files; sibling-draft-guard.ts already localizes it as a const. A shared exported constant would prevent a future typo'd literal from re-opening this class of bug.

🤖 Generated with Claude Code

Greptile Summary

The PR exempts the non-logging run-id sentinel from the task duplicate-awareness gate and adds a regression test for that context. However, the alternate Agent SDK in-app chat path supplies a generated UUID, so it remains subject to the gate.

  • Updates the duplicate-awareness guard to skip noLoggingRunId.
  • Documents why non-logging chat contexts cannot use the breadcrumb-based retry gate.
  • Adds coverage showing sentinel-based chat contexts create tasks despite existing open tasks.

Confidence Score: 4/5

The PR should not merge until the Agent SDK in-app chat path is also made consistent with the intended chat exemption.

Sentinel-based chat calls now bypass the duplicate gate, but SDK-backed in-app chat propagates a generated UUID and therefore still returns the duplicate-awareness response when open tasks exist.

Files Needing Attention: apps/server/src/mastra/tools/task/createTaskTool.ts and its duplicate-gate regression tests

Important Files Changed

FilenameOverview
apps/server/src/mastra/tools/task/createTaskTool.tsExempts the non-logging sentinel from the duplicate gate, bu
Show production surfaces and changed-file mapping

Production surfaces

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

Changed files → surfaces

  • apps/server/src/mastra/tools/__tests__/approval-flag-guard.test.tsno production surface mapped
  • apps/server/src/mastra/tools/task/__tests__/createTaskTool.duplicate-gate.test.tsno production surface mapped
  • apps/server/src/mastra/tools/task/createTaskTool.tsno production surface mapped