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:
- Query a command including
AgentRun__c. - Update that queried instance with
AccessLevel.USER_MODE. - Inspect
DmlException.getDmlFieldNames(0). - Update a new record containing only
IdandStatus__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.