An Azure service that provides an event-driven serverless compute platform.
Start with the weekdays in your own screenshot. The 8th and the 15th are both Saturdays, and neither ran. The 16th, a Sunday, didn't run either. The one Sunday that did run is the 9th. Every Saturday in that window is missing, so pull a longer range before calling it coincidence.
What you can eliminate
The error on the 14th didn't stop the schedule. The docs are explicit: "the timer trigger doesn't retry after a function fails. When a function fails, it isn't called again until the next time on the schedule."
A dead Python worker doesn't explain it either. The timer machinery lives in the Functions host, not in the Python worker: the host decides an occurrence is due, then hands the invocation to the worker. If the worker dies, the occurrence still fires and lands as a failed invocation. That explains your two errors, never the silence. It also means nothing in function_app.py can cause this, and nothing you change there will fix it.
A stuck lease doesn't last two days either. The host coordinates the timer through a blob lease of at most 60 seconds, renewed while it holds it, and it re-checks about once a minute if it can't take it.
That leaves two candidates: the host wasn't running the listener at all on the 15th and 16th, or the persisted schedule state was wrong. The second is checkable and I'll come back to it.
The hypothesis that fits
Without Always On, "the Functions runtime goes idle after a few minutes of inactivity" on an App Service plan, and you reactivate it either with an HTTP request or by accessing "your app in the Azure portal" (dedicated plan). An idle runtime has no listener, so the occurrence passes unobserved. Consumption scale-to-zero and a stopped app produce the same silence.
This exact symptom has been reported on Python before: a timer trigger that only fires when the portal is opened or an HTTP trigger arrives, with IsPastDue in the trigger details when it does.
That's why your off-schedule rows matter. The scheduled run failed on Friday at 05:00, a successful run follows at 08:56 the same morning, and two more failures land on Saturday morning. That reads like someone working the incident by hand rather than like routine traffic. Either way, the app was being touched at those moments and not at 03:00, which is what an idle app looks like from the outside.
Check each of those three rows on its own, because a cold start and a manual click produce two invocations in quick succession: opening the Functions blade or hitting Run calls the admin API, which starts the host, which fires the missed occurrence as past due, and then the click itself adds a second invocation. The 09:36:36 and 09:42:07 pair on the 15th has exactly that shape.
The trigger details tell them apart. A catch-up carries UnscheduledInvocationReason: IsPastDue with OriginalSchedule. A portal Test/Run or POST /admin/functions/{name} is logged with Reason='This function was programmatically called via the host APIs.' and never touches the schedule state.
What's already in your telemetry
No log-level change needed. These messages are logged at Information or Error and should be there now:
traces
| where timestamp >= datetime(2026-08-07) and timestamp < datetime(2026-08-19)
| where message contains "listener"
or message contains "timer trigger status"
or message contains "IsPastDue"
or message contains "Host started"
| project timestamp, cloud_RoleInstance, message, Category = tostring(customDimensions.Category)
| order by timestamp asc
Use contains, not has. has matches whole terms, so a multi-word fragment like "timer trigger status" silently returns nothing and the query looks like it worked. cloud_RoleInstance is the useful column: a change across the gap means the host was recycled. The exception detail for your two failures is in exceptions, not traces.
The artifact that settles it
The status blob, in the azure-webjobs-hosts container. The Host.Functions. segment is the generated type name the host creates for every function regardless of language, so the path isn't just your function name:
timers/<host id>/Host.Functions.<function name>/status
Your function name follows verbatim, hyphens and underscores included. If you can't find it, list timers/<host id>/ and take what's there.
Last = the 14th at 03:00 with Next = the 15th at 03:00 means the schedule state was intact and the host wasn't there. A Next that jumped forward means the state itself was wrong. LastUpdated against your 08:56 and 09:36 rows tells you whether those went through the listener.
Rule-outs
0 0 3 * * * is 03:00 UTC and the table showing 05:00 is CEST, so the cron is right. Indexing is fine, since a function_app.py that fails to import takes down every function defined in it, and the others kept running. Check for an AzureWebJobs.<function name>.Disabled app setting and confirm WEBSITE_TIME_ZONE is unset.
What would settle it
The hosting plan and, on Dedicated, whether Always On is on. Whether those off-schedule rows report IsPastDue. Whether cloud_RoleInstance changed across the gap. And whether a longer range confirms every Saturday is missing.
References
Drafted with help from Claude, disclosed per the Q&A AI usage policy. The retry behaviour, the idle behaviour without Always On, and the status blob layout were verified against the linked timer trigger and Dedicated plan pages and the timer extension's schedule monitor before posting.