Describe the bug
A v2 function that declares background mode in-source with export const config = { background: true } and is deployed with netlify deploy --no-build ends up half-applied:
- The Netlify API reports the function with
invoke_mode: "background".
- Callers get the documented instant
202 Accepted.
- But execution is killed at exactly the 60-second synchronous limit (
Duration: 60000 ms in function logs, the function body never completes), and AWS Lambda async-retries the killed invocation twice, so the body also runs 3 times.
An identical function using the legacy -background filename suffix gets the real 15-minute background budget and completes normally, on the same site, same deploy, same command.
Reproduced identically on netlify-cli 26.1.0 (first version whose bundler supports config.background, zip-it-and-ship-it 14.6.0) and 26.2.0 (latest). Runtime nodejs20.x, us-east-2, paid team plan (background functions enabled, proven by the suffixed control running >90s on the same site).
Observed (both probes are identical 90s sleepers that log START/END, fired at the same moment):
[probe-bg-config] 02:46:52.727Z INFO Duration: 60000 ms Memory Usage: 113 MB
[probe-bg-config] 02:46:52.743Z INFO PROBE probe-bg-config START 2026-07-23T02:46:52.743Z
[probe-bg-suffix-background] 02:46:52.815Z INFO Duration: 90335 ms Memory Usage: 104 MB
[probe-bg-suffix-background] 02:46:52.822Z INFO PROBE probe-bg-suffix-background START 2026-07-23T02:46:52.822Z
[probe-bg-suffix-background] 02:48:22.822Z INFO PROBE probe-bg-suffix-background END 2026-07-23T02:48:22.822Z
probe-bg-config (config only): killed at 60s, END never logs, 2 async retries follow. probe-bg-suffix-background (suffix): END at exactly START+90s. Same result on both CLI versions.
Expected: per the Background functions docs and the configuration defaults table, config.background: true should grant the 15-minute background execution limit. A function the API itself reports as invoke_mode: background and that 202-acks callers should not run under the 60s synchronous limit. The two declaration mechanisms should not diverge.
This is nasty in production: every observable signal (dashboard, API, 202s) says "background", so nothing errors at the caller. The function is silently killed mid-work and silently re-run twice, applying partial effects up to 3 times for non-idempotent work.
Possibly related internals: config.background only entered the in-source-config schema in zisi 14.6.0 (CLI 26.1.0); older CLIs silently strip it. netlify.toml [functions.<name>] background = true is dropped by normalizeFunctionsConfig at every version, so the toml route is no workaround. We've also observed in-source path routes being bundled and uploaded but 404ing on --no-build deploys (they only take effect via the build-pipeline manifest). It looks like CLI deploys upload enough for registration/routing (invocationMode) but timeout provisioning never switches to the background budget.
Workaround: the -background filename suffix (directory + entry file) behaves correctly at every CLI version.
Steps to reproduce
-
Create a project with two identical dependency-free v2 functions that sleep 90s:
functions/probe-bg-config/probe-bg-config.ts:
export const config = { background: true };
export default async () => {
console.log(`PROBE probe-bg-config START ${new Date().toISOString()}`);
await new Promise((resolve) => setTimeout(resolve, 90_000));
console.log(`PROBE probe-bg-config END ${new Date().toISOString()}`);
return new Response('ok', { status: 200 });
};
functions/probe-bg-suffix-background/probe-bg-suffix-background.ts: identical body (suffix variant also keeps the config export).
-
Deploy without the build pipeline:
npx -y netlify-cli@26.2.0 deploy --no-build --site <site> --dir public --functions functions --alias probe
-
Confirm both functions registered as background:
curl -s -H "Authorization: Bearer $NETLIFY_AUTH_TOKEN" \
"https://api.netlify.com/api/v1/sites/$SITE_ID/functions"
# both report "im": "background"
-
Invoke each once — both return 202 in ~0.2s:
curl -X POST "https://probe--<site>.netlify.app/.netlify/functions/probe-bg-config"
curl -X POST "https://probe--<site>.netlify.app/.netlify/functions/probe-bg-suffix-background"
-
Wait ~2.5 minutes, then read the logs:
npx -y netlify-cli@26.2.0 logs --source functions \
--url https://probe--<site>.netlify.app --since 30m
-
See that probe-bg-config shows Duration: 60000 ms with no END line (plus 2 unrequested retry STARTs), while probe-bg-suffix-background logs END at START+90s.
-
Optional: redeploy with netlify-cli@26.1.0 and repeat — identical results.
Configuration
[build]
command = "pnpm --filter @hopdrive/svc-s3... build"
publish = "public"
[functions]
directory = "functions"
[dev]
autoLaunch = false
(No [functions.<name>] blocks, no redirects. The deploy is --no-build, so the build section is inert.)
Environment
- netlify-cli: 26.1.0 and 26.2.0 (both reproduce)
- Node.js: 20.x locally and on the GitHub-hosted runner; functions runtime nodejs20.x
- OS: macOS 14 and ubuntu-latest (GitHub Actions) — same behavior
- Deploy:
netlify deploy --no-build --site <site> --dir public --functions functions --alias <alias>
Describe the bug
A v2 function that declares background mode in-source with
export const config = { background: true }and is deployed withnetlify deploy --no-buildends up half-applied:invoke_mode: "background".202 Accepted.Duration: 60000 msin function logs, the function body never completes), and AWS Lambda async-retries the killed invocation twice, so the body also runs 3 times.An identical function using the legacy
-backgroundfilename suffix gets the real 15-minute background budget and completes normally, on the same site, same deploy, same command.Reproduced identically on netlify-cli 26.1.0 (first version whose bundler supports
config.background, zip-it-and-ship-it 14.6.0) and 26.2.0 (latest). Runtime nodejs20.x, us-east-2, paid team plan (background functions enabled, proven by the suffixed control running >90s on the same site).Observed (both probes are identical 90s sleepers that log START/END, fired at the same moment):
probe-bg-config(config only): killed at 60s, END never logs, 2 async retries follow.probe-bg-suffix-background(suffix): END at exactly START+90s. Same result on both CLI versions.Expected: per the Background functions docs and the configuration defaults table,
config.background: trueshould grant the 15-minute background execution limit. A function the API itself reports asinvoke_mode: backgroundand that 202-acks callers should not run under the 60s synchronous limit. The two declaration mechanisms should not diverge.This is nasty in production: every observable signal (dashboard, API, 202s) says "background", so nothing errors at the caller. The function is silently killed mid-work and silently re-run twice, applying partial effects up to 3 times for non-idempotent work.
Possibly related internals:
config.backgroundonly entered the in-source-config schema in zisi 14.6.0 (CLI 26.1.0); older CLIs silently strip it.netlify.toml[functions.<name>] background = trueis dropped bynormalizeFunctionsConfigat every version, so the toml route is no workaround. We've also observed in-sourcepathroutes being bundled and uploaded but 404ing on--no-builddeploys (they only take effect via the build-pipeline manifest). It looks like CLI deploys upload enough for registration/routing (invocationMode) but timeout provisioning never switches to the background budget.Workaround: the
-backgroundfilename suffix (directory + entry file) behaves correctly at every CLI version.Steps to reproduce
Create a project with two identical dependency-free v2 functions that sleep 90s:
functions/probe-bg-config/probe-bg-config.ts:functions/probe-bg-suffix-background/probe-bg-suffix-background.ts: identical body (suffix variant also keeps the config export).Deploy without the build pipeline:
Confirm both functions registered as background:
Invoke each once — both return 202 in ~0.2s:
Wait ~2.5 minutes, then read the logs:
See that
probe-bg-configshowsDuration: 60000 mswith no END line (plus 2 unrequested retry STARTs), whileprobe-bg-suffix-backgroundlogs END at START+90s.Optional: redeploy with
netlify-cli@26.1.0and repeat — identical results.Configuration
(No
[functions.<name>]blocks, no redirects. The deploy is--no-build, so the build section is inert.)Environment
netlify deploy --no-build --site <site> --dir public --functions functions --alias <alias>