firebase / firebase/firebase-tools

Functions deploy: region resolution runs before param substitution, parameterized Firestore trigger database silently falls back to us-central1

Open
#11,020 1 comment 0 reactions 0 assignees View on GitHub
api: functions reproducible type: bug
Dominant language
TypeScript
Stars
4.5k
Forks
1.3k
Avg merge
1d 12h
Merged PRs (30d)
84

Description

### [REQUIRED] Environment info

**firebase-tools:** 15.28.2 (also reproduced on 15.26.0; regression range starts at 15.18.0, see below)

**Platform:** macOS

### [REQUIRED] Test case

A 2nd-gen Firestore trigger that declares its database with a param:

```ts
import { onDocumentWritten } from "firebase-functions/firestore";
import { defineString } from "firebase-functions/params";

const database = defineString("DATABASE", { default: "(default)" });

export const myFn = onDocumentWritten({ document: "col/{id}", database }, () => {});
```

The wire manifest carries the CEL literal `{{ params.DATABASE }}` in `eventTrigger.eventFilters.database`.

### [REQUIRED] Steps to reproduce

1. Project whose `(default)` Firestore database is in `eur3` (or `nam5`, anything in the dual-region mapping).
2. Functions codebase with the snippet above, `DATABASE=(default)` in `.env`.
3. `firebase deploy --only functions --debug`.

No project needed to see the mechanism: driving the published `lib/` directly (load the codebase via firebase-functions' `loader.loadStack` + `manifest.stackToWire`, feed `yamlToBuild`, stub `gcp/firestore.getDatabase` to return `{ locationId: "eur3" }` for `(default)`), `resolveDefaultRegionsForBuild` calls `getDatabase` with the literal `"{{ params.DATABASE }}"` and resolves `us-central1`. The same build with the literal `"(default)"` in the filter resolves `europe-west1`, so the param is the only difference. Happy to share the repro script.

### [REQUIRED] Expected behavior

The function deploys to `europe-west1`, matching what an identical function with a literal `database: "(default)"` gets via `FIRESTORE_DUAL_REGION_TO_REGION_MAPPING`.

### [REQUIRED] Actual behavior

The function deploys to `us-central1`, with only a debug-level trace:

```
Failed to resolve region for endpoint myFn. Defaulting to us-central1.
```

Root cause (file refs against v15.28.2 `src/`): `resolveDefaultRegionsForBuild` runs on the unresolved build (`deploy/functions/prepare.ts:336`), before `build.resolveBackend` (`prepare.ts:347`) where `params.resolveParams` and the CEL substitution of `eventFilters` happen. So `firestore.getDefaultRegion` (`services/firestore.ts:81-89`) passes the literal to `getDatabase`, the lookup throws, the error is swallowed at `logger.debug` (`prepare.ts:596-603`), and the region falls back to `us-central1`. The dual-region mapping (`gcp/location.ts:44`) is unreachable for any param-declared database. `ensureTriggerRegions` runs later on the resolved backend, so the Eventarc trigger is correctly pinned to `eur3` while the function sits in `us-central1`, and the deploy succeeds silently. The wrong region then sticks, since existing-endpoint regions take precedence on redeploys.

This regressed in #10471 (v15.18.0), which moved region resolution ahead of `toBackend` so VPC connector ids could be built with a known region; before that the database filter was already substituted at resolution time. Deferring resolution again would regress #10471, so the fix seems to be either running `params.resolveParams` before region resolution and threading the values into `getDefaultRegion` (prompting doesn't appear to depend on regions), or a best-effort resolution from the already-loaded dotenv values plus param defaults, which would cover all non-interactive deploys. Would you take either of those, or is there a direction you'd prefer? Separately, would you want the `logger.debug` swallow raised to a visible warning? It currently hides both this and genuine region-resolution failures unless `--debug` is on.

Workaround for anyone hitting this: declare an explicit `region` on the function, or set `FIREBASE_FUNCTIONS_DEFAULT_REGION` (note it applies to every region-less function, and changing the region of an existing function deletes and recreates it).

Contributor guide

Open the contributing guide

Research direction

Read deploy/functions/prepare.ts around resolveDefaultRegionsForBuild and build.resolveBackend, then inspect services/firestore.ts and gcp/location.ts. Reproduce the unresolved parameter case with the described loader, manifest, yamlToBuild, and stubbed getDatabase setup. Done means a parameterized Firestore database resolves to its mapped region without silently falling back to us-central1, while preserving the existing VPC connector behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
firebase, typescript
Domain
backend, cli, cloud
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
58/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.