Thursday, September 10, 2026

A Salesforce API 67 gotcha: updating queried records in user mode

I hit this while testing an agent workflow that runs under a dedicated service user. The model call completed successfully, but saving the command outcome failed with this:

System.DmlException: Operation failed due to fields being inaccessible on
Sobject AgentCommand__c, check errors on Exception or Result!

At first glance, it looked like a missing permission. The service user had edit access to AgentCommand__c, and its permission set granted access to the fields the worker updated. That turned out not to be the issue.

What changed

In API 67, Apex database operations run in user mode by default. That brings object permissions and field-level security into play for ordinary DML. without sharing does not change that; it affects record sharing, not whether DML validates field access.

The failure surfaced in code that looked harmless:

AgentCommand__c command = [
    SELECT Id, AgentRun__c, Status__c, ExpectedRunRevision__c
    FROM AgentCommand__c
    WHERE Id = :commandId
    FOR UPDATE
];

command.Status__c = 'Succeeded';
update command;

The command's AgentRun__c field is a required, non-reparentable master-detail relationship. It has to be supplied on insert, but it is not updateable. It is also not something that can be added as an editable field permission.

Although the code only changed Status__c, the SObject passed to update also contained AgentRun__c because it came from the query. In user mode, Salesforce validates the populated fields on that record. The unchanged master-detail field was enough to fail the update.

How I verified it

I ran a small anonymous Apex reproduction in the affected scratch org:

  1. Query a command including AgentRun__c.
  2. Update that queried instance with AccessLevel.USER_MODE.
  3. Inspect DmlException.getDmlFieldNames(0).
  4. Update a new record containing only Id and Status__c.

The first update failed and reported AgentRun__c. The second update succeeded. That ruled out a missing object permission and identified the relationship field as the problem.

The fix

Don't DML the queried record. Build a sparse update record with only the fields that the operation owns:

AgentCommand__c commandUpdate = new AgentCommand__c(
    Id = command.Id,
    Status__c = command.Status__c,
    Output__c = command.Output__c,
    ErrorCode__c = command.ErrorCode__c,
    ErrorMessage__c = command.ErrorMessage__c,
    LeaseToken__c = command.LeaseToken__c,
    LeaseExpiresAt__c = command.LeaseExpiresAt__c
);
update commandUpdate;

For our lifecycle code, several paths update commands that were loaded by queries: normal completion, timeout handling, lease recovery, and payload retention. I centralized those writes behind a helper:

private static void updateCommands(List<AgentCommand__c> commands) {
    if (commands == null || commands.isEmpty()) {
        return;
    }
    List<SObject> updateable = Security
        .stripInaccessible(AccessType.UPDATABLE, commands, true)
        .getRecords();
    update updateable;
}

Security.stripInaccessible removes fields the execution user cannot update, including the populated master-detail field. It is a useful safety net for a record that originated from a query. Sparse records are still the clearest approach when the set of changed fields is known.

The lesson

Required does not mean updateable. A required master-detail relationship is a good example: it is essential when creating the child record and invalid on a later update if reparenting is disabled.

When an API 67 user-mode update reports an inaccessible field, inspect DmlException.getDmlFieldNames(0) before changing permission sets. The offending field may be a system-managed field that should never be in the update payload in the first place.

Share This:    Facebook Twitter

0 comments:

Post a Comment

Total Pageviews

My Social Profiles

View Sonal's profile on LinkedIn

Tags

__proto__ $Browser Access Grants Accessor properties Admin Ajax AllowsCallouts Apex Apex Map Apex Sharing AssignmentRuleHeader AsyncApexJob Asynchronous Auth Provider AWS Callbacks Connected app constructor Cookie CPU Time CSP Trusted Sites CSS Custom settings CustomLabels Data properties Database.Batchable Database.BatchableContext Database.query Describe Result Destructuring Dynamic Apex Dynamic SOQL Einstein Analytics enqueueJob Enterprise Territory Management Enumeration escapeSingleQuotes featured Flows geolocation getGlobalDescribe getOrgDefaults() getPicklistValues getRecordTypeId() getRecordTypeInfosByName() getURLParameters Google Maps Governor Limits hasOwnProperty() Heap Heap Size IIFE Immediately Invoked Function Expression Interview questions isCustom() Javascript Javascript Array jsForce Lightning Lightning Components Lightning Events lightning-record-edit-form lightning:combobox lightning:icon lightning:input lightning:select LockerService Lookup LWC Manual Sharing Map Modal Module Pattern Named Credentials NodeJS OAuth Object.freeze() Object.keys() Object.preventExtensions() Object.seal() Organization Wide Defaults Override PDF Reader Performance performance.now() Permission Sets Picklist Platform events Popup Postman Primitive Types Profiles Promise propertyIsEnumerable() prototype Query Selectivity Queueable Record types Reference Types Regex Regular Expressions Relationships Rest API Rest Operator Revealing Module Pattern Role Hierarchy Salesforce Salesforce Security Schema.DescribeFieldResult Schema.DescribeSObjectResult Schema.PicklistEntry Schema.SObjectField Schema.SObjectType Security Service Components Shadow DOM Sharing Sharing Rules Singleton Slots SOAP API SOAP Web Services SOQL SOQL injection Spread Operator Star Rating stripInaccessible svg svgIcon Synchronous this Token Triggers uiObjectInfoApi Upload Files VSCode Web Services XHR
Scroll To Top