Libra CodeHub

CedarCopilot/cedar-mail

Branch: staging

fix(config): disambiguate custom field id vs fieldKey for run-sql/list-fields

merged#2897CedarCopilot

CedarCopilot wants to merge 1 commit into staging from worktree-send-it-field-id-ambiguity

Live on prod, watching, 2 days leftTimeline and evidence
  1. Opened
    Sep 23, 2026, 12:40 AM
  2. Sep 23, 2026, 12:48 AM
  3. Merged
    Sep 23, 2026, 12:58 AM
  4. Live on staging
    Sep 23, 2026, 12:58 AM
  5. Live on prod
    Sep 23, 2026, 12:58 AM
  6. Observed 0 hours
    Sep 23, 2026, 12:58 AM
  7. Watching

    Live on prod, watching, 2 days left

    Sep 23, 2026, 12:58 AM
  8. Pipelines steady after this deploy
    Sep 23, 2026, 12:58 AM
  9. Pipelines steady after this deploy
    Sep 23, 2026, 12:58 AM

Behaviors Libra is checking

The config tool's list-fields output clearly identifies `fieldKey` as the value to use for run-sql `field_id` and `wm_<fieldKey>` filters, while marking `id` as an unrelated migration-only identifier.Not checked
prod
The config `list-fields` output clearly identifies `fieldKey` as the value to use for custom-field `run-sql` `field_id` and `wm_<fieldKey>` filters, while warning that the nested opaque `id` is only for rename and migration tracking.Not checked
staging

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.

Summary

  • A custom field has two separate, unrelated identifiers , fieldKey (the object key in customFieldDefinitions, and the actual value stored as field_id in crm_conversation_field_values) and definition.id (an opaque rename-tracking identifier that is never a valid query key for an ordinary custom field). config-read's list-fields action returned both with no indication of which is which, and an agent this session picked definition.id for a run-sql query, got a silent zero-row false negative, and nearly reported a real field as having no data.
  • Adds a disambiguating .describe() to FieldDefinitionDataSchema.id (config-tool-shared.ts) so the correct guidance travels with the tool's own response, mirrors the same fix into the canonical CustomFieldDefinition.id TSDoc in aop-schema.ts (which had the identical stale claim), and adds matching gotchas to the run-sql and field-query skill docs.
  • While writing the disambiguation, found and documented (but did not fix , separate, higher-risk issue) a real pre-existing bug: config-write-tool.ts's delete-field and remove-field-option actions key their crm_conversation_field_values lookups off definition.id instead of fieldKey, via field-deletion-safety.ts and option-reconciliation.ts , for a field whose id ≠ fieldKey (the common case for ordinary custom fields), this means deleting a custom field likely doesn't delete its stored values, and removing a picklist option likely doesn't find/remap the values that used it.

Test plan

  • apps/server/src/mastra/tools/config/__tests__/config-read-output-schema.test.ts , new test asserting FieldDefinitionDataSchema.id's description warns against use as a query key and points to fieldKey; 3/3 pass.
  • pnpm --filter @zero/server exec vitest run src/mastra/tools/config/__tests__ , 169/169 pass.
  • pnpm --filter @zero/server exec vitest run src/ (full server suite) , 1136 passed, 34 skipped.
  • pnpm --filter @zero/mail exec jest --config jest.config.cjs --no-coverage --ci under TZ=UTC (matching this repo's CI env for that step) , 540/540 suites pass. (One unrelated pre-existing test, tests/modules/chat-store/chatHistoryList.test.ts, is a local-timezone-only flake outside the local ambient TZ; verified it passes cleanly under TZ=UTC and is untouched by this diff.)
  • pnpm --filter @zero/mail run test:tz , 5/5 pass.
  • pnpm --filter @zero/server run types (tsc -b) , clean.
  • pnpm deps:check , clean, no violations.
  • Three rounds of fresh, context-free thermo review (no author bias) , first pass found two 🟔 (an overclaiming description, a misleading worked example), both fixed; second pass found one more 🟔 (the aop-schema.ts TSDoc still disagreed with the fixed schema description), fixed; third pass came back clean , 0 šŸ”“, 0 🟔.

Verify in prod

This is a documentation/schema-description change only , no new runtime branching, no new log lines, and no behavior change to any tool response's actual data. There is nothing to query in Axiom/CloudWatch for this change itself; the correctness of the fix was verified via code tracing and tests (see above), not runtime telemetry. The one thing worth confirming post-merge, informally, is that the next agent to query a custom field's field_id via run-sql or list-fields uses fieldKey rather than id , but that's an agent-behavior outcome, not something with a dedicated metric.

šŸ¤– Generated with Claude Code

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/.claude/skills/field-query/SKILL.mdno production surface mapped
  • apps/server/.claude/skills/run-sql/SKILL.mdno production surface mapped
  • apps/server/src/db/aop-schema.tsno production surface mapped
  • apps/server/src/mastra/tools/config/__tests__/config-read-output-schema.test.tsno production surface mapped
  • apps/server/src/mastra/tools/config/config-tool-shared.tsno production surface mapped