Scheduled function returns 500 before handler (no Function Logs) while other functions OK

Context

  • Site: punterx-core-staging

  • Base URL: https://punterx-core-staging.netlify.app

  • Unique deploy tested: https://68d3be1e3d8cd8b58182890f--punterx-core-staging.netlify.app

Functions involved

  • cron-match (scheduled every 15 minutes)

  • diag-rt (unscheduled) — serves 200 JSON as health check

netlify.toml (relevant)

[functions]
  directory = "netlify/functions"
  included_files = ["netlify/functions/_lib/**"]

[functions."cron-match"]
  schedule = "*/15 * * * *"

Handler (minimal top part of netlify/functions/cron-match.cjs)

'use strict';
try { console.log("[cron-match] TOP: module load", new Date().toISOString()); } catch(_) {}

exports.handler = async (event, context) => {
  try {
    const qs = (event && event.queryStringParameters) || {};
    if (qs && qs.noop === "1") {
      try { console.log("[cron-match] NOOP"); } catch(_){}
      return { statusCode: 200, headers: { 'content-type': 'application/json' },
        body: JSON.stringify({ ok:true, mode:"noop", scheduled: !!(event&&event.headers&&event.headers['x-nf-scheduled']), tag:"[SAFE_SHIM]" }) };
    }
  } catch (_){}

  const scheduled = !!(event && event.headers && event.headers['x-nf-scheduled']);
  try { console.log("[cron-match] enter", { scheduled }); } catch(_){}
  return { statusCode: 200, headers: { 'content-type': 'application/json' },
    body: JSON.stringify({ ok:true, scheduled, tag:"[SAFE_SHIM]" }) };
};

What happens

  • GET {unique-deploy}/.netlify/functions/diag-rt200 JSON :white_check_mark:

  • GET {unique-deploy}/.netlify/functions/cron-match?noop=1500 text/plain :cross_mark:

    • Response shows only Internal Error. ID: <x-nf-request-id>

    • No entries appear in Function Logs for those request IDs (suggests the error is pre-handler).

  • A temporary unscheduled alias (cron-health) responding with the same SAFE_SHIM returned 200 JSON when unscheduled; when scheduled, it behaved like cron-match (500 + no logs).

Recent request IDs (failing 500s)

  • 01K5XJ414X00D3WJW8HGAP58QZ

  • 01K5XJ5C23NHQHK93ZDFXJFMVA
    (others available if helpful)

Diagnostics

  • diag-rt returns OK with Node seen as 20.19.4 / 22.18.0 depending on instance.

  • A diagnostic function (diag-cron-bundle) reports:

{
  "notes": ["Listed functions dir and hashed cron entry (auto)"],
  "cron": { "missing": true },
  "here": "/var/task"
}

What I’ve tried

  • Ensured only one entry file exists for this function; removed backups and prebuilt ZIPs/manifest from the repo.

  • Tried both cron-match.cjs and cron-match-log.cjs names; also tested .js vs .cjs.

  • Renamed function (cron-match-logcron-match) and updated netlify.toml.

  • Deployed with --skip-functions-cache multiple times; local builds cleaned.

  • Verified other functions (including core-runner and diag-rt) serve 200 in the same deploy.

  • Confirmed scheduled behavior and aware that scheduled functions aren’t URL invokable in production per docs; issue here is that even a NOOP query to the same handler (when scheduled) returns 500 before the handler runs (no Function Logs), whereas the unscheduled alias hits the handler and returns 200.

What I need help with

  • Is the 500 pre-handler with no Function Logs the expected behavior for a scheduled function when hit via URL in production, or should it be a 404/clearer response?

  • If 500 is expected, is there a recommended pattern to expose a safe debug/health path for the same code without scheduling conflicts (beyond maintaining a separate unscheduled alias)?

  • Anything obvious in my setup that would cause the runtime to treat the scheduled function’s URL route differently from others (given diag-rt and core-runner work fine)?

Environment

  • Netlify CLI: 23.6.0

  • OS: GitHub Codespaces (Linux)

  • Node (observed in responses): 20.19.4 / 22.18.0

Thanks!