Libra CodeHub

CedarCopilot/cedar-mail

Branch: staging

fix(tools): reject unknown columnIds in find-crm-conversations instead of silently dropping them

merged#2573CedarCopilot

CedarCopilot wants to merge 1 commit into staging from fix/find-crm-unknown-column-ids

Live on prod, no production signal yetTimeline and evidence
  1. Opened
    Aug 24, 2026, 8:30 PM
  2. Aug 24, 2026, 8:42 PM
  3. Merged
    Aug 25, 2026, 7:27 AM
  4. Live on prod
    Aug 25, 2026, 7:27 AM
  5. Observed 34 hours
    Aug 25, 2026, 7:27 AM
  6. Pipelines steady after this deploy
    Aug 25, 2026, 7:27 AM
  7. Unobserved

    Live on prod, no production signal yet

    Aug 26, 2026, 5:38 PM

Behaviors Libra is checking

Chat workflow behavior from conversation/findCRMConversationsTool.ts keeps working in prod.Inconclusivelow confidence

Strict CloudWatch fallback found 3 prod failure log lines for mirror.upsertEmailThread failed (write CONNECTION_CLOSED aws-1-us-east-2.pooler.supabase.com:6543), but this domain-wide failure family is not tied to this intent.

prod, checked Aug 26, 2026, 5:38 PM

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

The bug

find-crm-conversations in column_filters mode normalized filters through a switch (columnId). An id with no matching case fell through with no error and no warning: the filter was dropped, the query ran unfiltered, and the tool still returned success: true. The caller has no way to tell "no deals matched" apart from "your filter never applied".

Found in production. A cron subagent filtered { columnId: "hasTodoTasks", binaryChoice: "false" } to skip deals that already have an open follow-up task, got the entire pipeline back, and only avoided acting on it because the model noticed tasks in the results and hand-filtered them out.

hasTodoTasks is a particularly easy id to guess wrong, because it is not wrong anywhere else: it is the name currentTasks normalizes into (findCRMConversationsTool.ts:583 before this change), and it is the spelling the sibling normalizer at services/crm/conversations.ts:6601 accepts.

The fix

  • COLUMN_FILTER_NORMALIZERS replaces the switch: one normalizer per canonical column id. The map is now the single source of truth for what is filterable, so the validator cannot drift from the implementation the way a hand-maintained list would.
  • validateColumnFilterColumns rejects any id that is neither in the map nor wm_-prefixed, and names the valid ids in the error so the caller does not just guess again. Same fail-loud shape as the existing validateColumnFilterKinds guard, which was added for the identical class of bug (silently-dropped searchText on select-only columns).
  • Alias hasTodoTasks → currentTasks, rather than failing a caller who guessed the internal name.
  • Name the filterable ids in the schema description. columnId was documented only as 'Column ID to filter', which is what made guessing inevitable.

Behavior change worth knowing: a call that previously succeeded-with-a-silently-ignored-filter now returns an error. That is the point, but it means an agent in the habit of passing a junk id will start seeing failures instead of quietly wrong result sets.

Verification

  • pnpm --filter @zero/server exec vitest run src/mastra/tools/conversation/__tests__/findCRMConversationsTool.test.ts , 16 passed, including 3 new cases: unknown id rejected, hasTodoTasks accepted as an alias, wm_ custom fields still pass.
  • pnpm --filter @zero/server run types , clean.
  • pnpm deps:check , no violations (1543 modules, 8064 dependencies).

Note

findCRMConversationsTool.ts is 1304 lines, up 32 from 1272. It was already over the 1k budget before this change; decomposing it is worth doing but is out of scope here.

šŸ¤– Generated with Claude Code

Greptile Summary

This PR makes find-crm-conversations fail loudly when column_filters contains an unsupported column ID while preserving custom fields and documented aliases.

  • Replaces switch-based normalization with a canonical normalizer map.
  • Adds hasTodoTasks as an alias for currentTasks.
  • Validates filter column IDs before querying and returns actionable errors.
  • Documents supported IDs and adds regression coverage for rejection, aliasing, and custom fields.

Confidence Score: 5/5

The PR appears safe to merge, with no concrete changed-code failure identified.

The validator and normalizer use the same canonical map, aliases are normalized before lookup, and custom-field filters continue through their dedicated conversion path.

Important Files Changed

FilenameOverview
apps/server/src/mastra/tools/conversation/findCRMConversationsTool.tsAdds canonical column-filter normalization and validation while preserving aliases and the separate custom-field conversion path; no actionable regression was established.
apps/server/src/mastra/tools/conversation/tests/findCRMConversationsTool.test.tsAdds focused regression tests covering unknown
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/conversation/__tests__/findCRMConversationsTool.test.tsno production surface mapped
  • apps/server/src/mastra/tools/conversation/findCRMConversationsTool.tsno production surface mapped