netlify / netlify/cli

Functions with config.background: true run with the 60s synchronous timeout when deployed via netlify deploy

Open
#8,354 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

type: bug
Dominant language
TypeScript
Stars
1.9k
Forks
474
Avg merge
23h 30m
Merged PRs (30d)
53

Description

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
  1. 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).

  2. Deploy without the build pipeline:

    npx -y netlify-cli@26.2.0 deploy --no-build --site <site> --dir public --functions functions --alias probe
    
  3. 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"
    
  4. 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"
    
  5. 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
    
  6. 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.

  7. 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>

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by tracing the --no-build deployment path and normalizeFunctionsConfig, comparing in-source config.background handling with the -background filename suffix and the timeout provisioning path. Reproduce with the two 90-second functions from the issue and verify that the config form receives the 15-minute background limit, completes once, and does not retry after 60 seconds.

Written by the indexing model from the issue text.

Assessment

Tech stack
node.js, typescript
Domain
backend, cli, cloud
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.