standard-object-quirks
Guidance on non-obvious runtime behaviors of Salesforce standard objects — polymorphic lookups, lead conversion field loss, PersonAccount dual-nature, CaseComment trigger isolation, and Activity date fields. NOT for schema documentation or data modeling; NOT for custom object design; NOT for field-level security configuration.
Best use case
standard-object-quirks is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Guidance on non-obvious runtime behaviors of Salesforce standard objects — polymorphic lookups, lead conversion field loss, PersonAccount dual-nature, CaseComment trigger isolation, and Activity date fields. NOT for schema documentation or data modeling; NOT for custom object design; NOT for field-level security configuration.
Teams using standard-object-quirks should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/standard-object-quirks/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How standard-object-quirks Compares
| Feature / Agent | standard-object-quirks | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Guidance on non-obvious runtime behaviors of Salesforce standard objects — polymorphic lookups, lead conversion field loss, PersonAccount dual-nature, CaseComment trigger isolation, and Activity date fields. NOT for schema documentation or data modeling; NOT for custom object design; NOT for field-level security configuration.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# Standard Object Quirks
This skill activates when a practitioner encounters unexpected runtime behavior from Salesforce standard objects — situations where the platform does something that contradicts reasonable assumptions drawn from the UI or general database experience. It provides doc-grounded explanations and corrective patterns for polymorphic lookup behavior, lead conversion field mapping gaps, PersonAccount dual-nature pitfalls, CaseComment trigger isolation, and Activity date-field confusion.
---
## Before Starting
Gather this context before working on anything in this domain:
- Which standard objects are involved? Confirm whether the org uses PersonAccounts (check `Account.IsPersonAccount` field availability), and whether Lead conversion or Activity automation is in scope.
- The most common wrong assumption is that standard objects behave like custom objects. They do not: standard objects have hard-coded platform behaviors (cascade rules, polymorphic fields, dual-nature records) that cannot be overridden by configuration.
- Key limits: polymorphic fields cannot be traversed in a single SOQL relationship query; Lead conversion field mapping only transfers mapped fields; PersonAccount records share a single Id across Account and Contact but expose different field sets depending on SOQL target.
---
## Core Concepts
### Polymorphic Lookups (WhoId / WhatId)
Task and Event use polymorphic lookup fields: `WhoId` points to either a Contact or a Lead, while `WhatId` points to Account, Opportunity, Case, or any of 20+ standard and custom objects. You cannot traverse both sides of a polymorphic relationship in a single SOQL query using dot notation. Instead, you must use `TYPEOF` in SOQL or query each target type separately. Attempting `SELECT Who.Name FROM Task` works, but `SELECT Who.Email FROM Task` fails because the runtime does not know which sObject type `Who` resolves to at compile time.
### Lead Conversion Field Mapping
When a Lead is converted, Salesforce creates or updates a Contact, optionally an Account, and optionally an Opportunity. Only fields that are explicitly mapped in Lead Field Mapping (Setup > Object Manager > Lead > Fields & Relationships > Map Lead Fields) are transferred. Unmapped custom fields on the Lead are silently lost. Standard field mappings are pre-configured but custom fields require manual mapping. If a Lead has a value in an unmapped custom field, that data is permanently lost after conversion unless captured by a before-conversion trigger.
### PersonAccount Dual-Nature
A PersonAccount is simultaneously an Account record and a Contact record sharing a single Salesforce Id. In SOQL, PersonAccount-specific fields use the `Person` prefix on the Account object (e.g., `PersonEmail`, `PersonMailingCity`). Querying the `Email` field on Account returns null for PersonAccounts — you must use `PersonEmail`. The Contact record associated with a PersonAccount has its own Id but is not independently deletable. Apex triggers on Account fire for PersonAccount changes, but triggers on Contact also fire for the implicit Contact side of the PersonAccount.
### CaseComment Trigger Isolation and Activity Date Fields
CaseComment is a child of Case, but DML operations on CaseComment do not fire Case triggers. If you need Case-level automation when a comment is added, you must write a trigger on CaseComment that explicitly updates the parent Case. Separately, on the Task object, `ActivityDate` is the due date, not the completion date. The completion date is `CompletedDateTime` (available only when Status is Completed). For Event, `EndDateTime` is required when creating records via API or Apex even though the UI allows specifying only a duration.
---
## Common Patterns
### Safe Polymorphic Query Pattern
**When to use:** You need to query Tasks and get related Contact or Lead fields.
**How it works:**
```apex
List<Task> tasks = [
SELECT Id, Subject,
TYPEOF Who
WHEN Contact THEN FirstName, LastName, Email
WHEN Lead THEN FirstName, LastName, Company
END
FROM Task
WHERE OwnerId = :UserInfo.getUserId()
];
for (Task t : tasks) {
if (t.Who instanceof Contact) {
Contact c = (Contact) t.Who;
System.debug('Contact email: ' + c.Email);
} else if (t.Who instanceof Lead) {
Lead l = (Lead) t.Who;
System.debug('Lead company: ' + l.Company);
}
}
```
**Why not the alternative:** Using `Who.Name` alone works but gives you no type-specific fields. Attempting dot-notation on a polymorphic field for type-specific fields causes a compile-time error.
### Lead Conversion Field Preservation Pattern
**When to use:** Custom fields on Lead must survive conversion without manual field mapping.
**How it works:**
1. Create a before-trigger on Lead that fires on the `convertLead` operation.
2. In the trigger, copy custom field values to a staging custom object or to the target Contact/Account via a lookup established pre-conversion.
3. Alternatively, build a Flow that runs on Lead conversion to capture and transfer unmapped values.
4. Post-conversion, verify the target records contain the expected data.
**Why not the alternative:** Relying solely on Lead Field Mapping requires manual setup per field and is easy to miss when new custom fields are added. The trigger-based approach is self-maintaining.
---
## Decision Guidance
| Situation | Recommended Approach | Reason |
|---|---|---|
| Need Contact/Lead fields from Task | Use TYPEOF in SOQL | Polymorphic lookups cannot use standard dot-notation for type-specific fields |
| PersonAccount email in reports or queries | Query `PersonEmail` on Account | `Email` field on Account is null for PersonAccounts |
| Case automation on comment addition | CaseComment trigger that updates parent Case | CaseComment DML does not fire Case triggers |
| Preserving custom Lead fields through conversion | Before-conversion trigger or post-conversion Flow | Unmapped custom fields are silently dropped |
| Creating Events via Apex | Always set EndDateTime explicitly | API requires it even though UI derives it from Duration |
---
## Recommended Workflow
Step-by-step instructions for an AI agent or practitioner diagnosing a standard object quirk:
1. **Identify the standard object** — Confirm exactly which standard object is exhibiting unexpected behavior. Check if PersonAccounts are enabled (`SELECT IsPersonAccount FROM Account LIMIT 1`).
2. **Reproduce the behavior** — Write a minimal SOQL query or Apex snippet that demonstrates the unexpected result. Compare the API behavior against what the UI shows.
3. **Check known quirks** — Review this skill's Core Concepts and Gotchas sections. Most standard-object surprises fall into one of five categories: polymorphic lookups, lead conversion, PersonAccounts, CaseComment isolation, or Activity date fields.
4. **Apply the corrective pattern** — Use the matching pattern from Common Patterns. If the issue is a polymorphic query, switch to TYPEOF. If it is PersonAccount, switch to Person-prefixed fields. If it is CaseComment, add a dedicated trigger.
5. **Validate with unit tests** — Write an Apex test that asserts the corrected behavior. For polymorphic queries, test with both Contact and Lead WhoId values. For PersonAccounts, test with both Business and Person account types.
6. **Document the quirk locally** — Add a comment in the code explaining the platform behavior, so future maintainers do not revert to the naive pattern.
---
## Review Checklist
Run through these before marking work in this area complete:
- [ ] All SOQL queries on Task/Event use TYPEOF or explicit type checks for WhoId/WhatId
- [ ] PersonAccount queries use Person-prefixed fields (PersonEmail, PersonMailingCity) not standard Contact fields on Account
- [ ] Lead conversion logic accounts for unmapped custom fields with explicit mapping or trigger-based preservation
- [ ] CaseComment-driven automation uses a CaseComment trigger, not a Case trigger
- [ ] Event creation via Apex sets EndDateTime explicitly
- [ ] Unit tests cover both sides of polymorphic lookups (Contact and Lead for WhoId)
- [ ] Code comments explain the non-obvious platform behavior at each quirk site
---
## Salesforce-Specific Gotchas
Non-obvious platform behaviors that cause real production problems:
1. **Account deletion does not cascade-delete Contacts** — Deleting an Account does not delete its child Contacts. Instead, Contacts are unlinked (AccountId set to null) if they have no other master-detail relationships. This contradicts the assumption that parent deletion removes children.
2. **Task CompletedDateTime vs ActivityDate** — `ActivityDate` on Task is the due date, not the completion date. The actual completion timestamp is `CompletedDateTime`, which is only populated when the Task Status equals "Completed." Automation that checks `ActivityDate` to detect completed tasks will produce wrong results.
3. **PersonAccount Contact triggers fire unexpectedly** — When a PersonAccount is updated, triggers on both Account and Contact execute because the platform maintains an implicit Contact record. Trigger logic that assumes Contact triggers only fire for standalone Contact records will run on PersonAccount updates too, potentially causing errors or double-processing.
---
## Output Artifacts
| Artifact | Description |
|---|---|
| Corrected SOQL query or Apex snippet | Revised code that accounts for the identified standard-object quirk |
| Quirk documentation comment | Inline code comment explaining the non-obvious behavior for future maintainers |
| Unit test class | Test coverage proving the corrected behavior under both normal and edge-case scenarios |
---
## Related Skills
- data-model-documentation — Use when you need to document the overall schema design rather than diagnose a runtime behavioral quirk
- validation-rules — Use when the fix for a standard object quirk involves adding a validation rule to enforce data integrity
- sharing-and-visibility — Use when the quirk involves record access, OWD, or sharing rule behavior on standard objectsRelated Skills
salesforce-connect-external-objects
Use when deciding whether Salesforce Connect and External Objects are the right fit for external data access, or when reviewing OData, cross-org, and custom adapter patterns, query limitations, and latency tradeoffs. Triggers: 'Salesforce Connect', 'External Objects', '__x', 'OData adapter', 'custom adapter'. NOT for ordinary ETL or replicated-data designs where the data should live inside Salesforce.
flow-cross-object-updates
Cross-object DML in Flow: updating parent from child or child from parent via Get/Update Records, lookup traversal in formulas, and bulkification. NOT for Apex cross-object updates. NOT for Process Builder (migrate-workflow-pb covers migration). Use when building flows that must write to a related record.
external-data-and-big-objects
Use this skill when storing large historical datasets in Salesforce using Big Objects, querying them with Async SOQL, or deciding between Big Objects and External Objects for high-volume or external data access patterns. Trigger keywords: big object, async SOQL, AsyncQueryJob, external object, Salesforce Connect, IoT data, audit history, event log archival, Database.insertImmediate, composite index. NOT for Salesforce Connect adapter configuration or OAuth setup (use salesforce-connect-external-objects), and NOT for standard data archival strategies (use data-archival-strategies).
data-cloud-data-model-objects
Use when designing or managing Data Model Objects (DMOs) in Salesforce Data Cloud — covers DMO schema design, subject area governance, data relationships between DMOs, XMD (extended metadata) layer management, data transforms (streaming vs. batch), and mandatory DMO requirements for identity resolution. NOT for standard Salesforce CRM object design, Data Cloud data stream configuration, or SOQL queries against Data Cloud objects.
omnistudio-vs-standard-decision
Decision framework for choosing OmniStudio (OmniScript, FlexCards, Integration Procedures) vs standard Salesforce tooling (Screen Flow, LWC, Apex) for guided UI and data transformation use cases: capability matrix, license availability gate, team skills assessment, and migration path from managed package to Standard Designers. NOT for OmniStudio implementation or configuration.
omnistudio-vs-standard-architecture
Architecture decision framework for choosing between OmniStudio and the standard Salesforce platform (Screen Flow, LWC, Apex) for guided UI and data orchestration use cases. Covers the license gate, the Dynamic Forms → Screen Flow → OmniStudio continuum, Standard Runtime vs Vlocity managed package migration debt, and team skill considerations. NOT for implementation. NOT for OmniScript development or FlexCard configuration.
cpq-vs-standard-products-decision
Use when deciding whether to implement Salesforce CPQ or stay with standard Products and Pricebooks for quoting and pricing. Triggers: 'should we buy CPQ or use standard pricebooks', 'is CPQ worth the cost for our quoting process', 'product bundling without CPQ', 'guided selling vs manual product selection', 'complex pricing rules or multi-dimensional discounting'. NOT for CPQ implementation details, NOT for CPQ package installation or configuration, NOT for Revenue Cloud Advanced.
test-class-standards
Use when writing, reviewing, or debugging Apex test classes, test data factories, async test behavior, negative-path assertions, or callout mocking. Triggers: 'SeeAllData', 'Test.startTest', 'HttpCalloutMock', 'test data factory', 'missing assertions'. NOT for LWC Jest tests or code-coverage-only conversations detached from test design.
mixed-dml-and-setup-objects
Use when encountering or preventing MIXED_DML_OPERATION errors caused by DML on setup objects (User, UserRole, PermissionSet, Group, GroupMember) in the same transaction as non-setup objects. Triggers: 'MIXED_DML_OPERATION', 'setup object DML error', 'cannot insert User and Account together', 'System.runAs mixed DML', '@future setup object workaround'. NOT for general async Apex patterns — see async-apex. NOT for test data factory structure — see test-data-factory-patterns.
cross-object-formula-and-rollup-performance
Use when diagnosing or preventing performance problems caused by cross-object formula spanning relationships and roll-up summary field recalculations — especially in LDV orgs. Triggers: 'roll-up summary timing stale in trigger', 'Maximum 15 object references error', 'rollup recalculation timeout on large child set'. NOT for formula syntax authoring (use admin/formula-fields), formula compile-size limits (use apex/formula-field-performance-and-limits), or general governor-limit troubleshooting.
apex-switch-on-sobject
Apex switch-on-SObjectType patterns — type dispatching across SObject collections, polymorphic handlers, the typed-variable binding that makes `when SObjectType varName` more than a tag check. Covers the single-type-per-when-block restriction, the null branch, the no-fall-through rule, and why `Type.forName()` cannot be used in a switch expression. NOT for basic Apex switch syntax on Integer / String / enum (use Apex Developer Guide directly), NOT for dynamic field access (see apex/dynamic-apex).
salesforce-object-queryability
Distinguish the six real reasons a Salesforce query can 'fail', and the protocol for diagnosing before declaring. Covers: object doesn't exist, not queryable in edition, permission-denied, field-level errors, namespace prefix missing, API version mismatch. NOT for SOQL performance tuning (use soql-optimization-patterns). NOT for Bulk API payload issues (use bulk-api-2-patterns).