How to re-span a cron-like launchd script in case of script error?
It seems it can be done partially. Basically the problem was that KeepAlive in combination with SuccessfulExit implied a RunAtLoad (the program was launched right at the start and not at the specified StartCalendarIntervall.) Setting the additional parameter AfterInitialDemand (which is undocumented) will change this behaviour and the program is first launched at the specified calendar time:

&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"&gt;
&lt;plist version="1.0"&gt;
&lt;dict&gt;
    &lt;key&gt;Label&lt;/key&gt;
    &lt;string&gt;com.example.backup&lt;/string&gt;
    &lt;key&gt;ProgramArguments&lt;/key&gt;
    &lt;array&gt;
        &lt;string&gt;/Volumes/Example/backup.sh&lt;/string&gt;
    &lt;/array&gt;

    &lt;key&gt;StartCalendarInterval&lt;/key&gt;
    &lt;array&gt;
        &lt;dict&gt;
            &lt;key&gt;Hour&lt;/key&gt;
            &lt;integer&gt;2&lt;/integer&gt;
            &lt;key&gt;Minute&lt;/key&gt;
            &lt;integer&gt;15&lt;/integer&gt;
        &lt;/dict&gt;
    &lt;/array&gt;

    &lt;key&gt;KeepAlive&lt;/key&gt;
    &lt;dict&gt;
        &lt;key&gt;SuccessfulExit&lt;/key&gt;
        &lt;false/&gt;
        &lt;key&gt;AfterInitialDemand&lt;/key&gt;
        &lt;true/&gt;
    &lt;/dict&gt;
    &lt;key&gt;ThrottleInterval&lt;/key&gt;
    &lt;integer&gt;3600&lt;/integer&gt;

    &lt;key&gt;StandardErrorPath&lt;/key&gt;
    &lt;string&gt;/var/log/com.example/backup_error&lt;/string&gt;
    &lt;key&gt;StandardOutPath&lt;/key&gt;
    &lt;string&gt;/var/log/com.example/backup_output&lt;/string&gt;
&lt;/dict&gt;
&lt;/plist&gt;


The only problem is that if the program/ script fails then ThrottleInterval will overrule the StartCalendarInterval, so depending on time of error and the set interval the program/ script would not launch exactly at the specified calendar time after a day has elapsed (assuming it failed until then) and might continue at odd time offsets (until it succeeds.)

But to sum it up: the trick is the undocumented AfterInitialDemand set to true.