A scheduled SharePoint job runs twice after a timeout. Now the calendar has duplicate meetings, the directory has duplicate employees, or the document library contains two copies with slightly different names. The job technically “worked” both times.
The problem: creation is easier than identity
Developers often search by title before creating an item. Titles are poor identifiers: “Advisory Meeting” can legitimately occur every month, and an employee’s display name can change.
The quirk: SharePoint item IDs are only stable inside one list
If the source is Banner, an ICS feed, or another SharePoint site, the destination item ID cannot identify the original record. I build a deterministic external key from stable source values.
$sourceKey = implode('|', [
'attendance-feed',
strtolower(trim($title)),
$startUtc,
$endUtc
]);
$externalKey = hash('sha256', $sourceKey);
For a person, a Banner ID or immutable account object ID is better than email because email addresses and names can change.
$existing = findByExternalKey($externalKey);
if (!$existing) {
createSharePointItem($fields + ['ExternalKey' => $externalKey]);
} elseif ($existing['DataHash'] !== hashData($fields)) {
updateSharePointItem($existing['ID'], $fields);
} else {
$status = 'unchanged';
}
The workaround: make reruns boring
A second run should report unchanged records—not create another set. I finish with counts for created, updated, unchanged, deactivated, and failed items. When automation is idempotent, retries stop being scary and become a normal recovery tool.
