Skip to content

fix: use the modular FieldValue so the functions work in the emulator - #2989

Merged
emcelroy merged 2 commits into
masterfrom
fix-fieldvalue-under-functions-emulator
Sep 1, 2026
Merged

fix: use the modular FieldValue so the functions work in the emulator#2989
emcelroy merged 2 commits into
masterfrom
fix-fieldvalue-under-functions-emulator

Conversation

@emcelroy

@emcelroy emcelroy commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

The problem

Running the AI analysis pipeline against the Firebase emulators fails as soon as a function reaches a serverTimestamp() call:

TypeError: Cannot read properties of undefined (reading 'serverTimestamp')
⚠  Your function was killed because it raised an unhandled error.

The emulator stops the function, nothing is recorded, and for the queue functions the entry is left in place. Those triggers have no retry configured, so the document is never processed again.

Why it happens

The functions emulator replaces the firebase-admin module with a proxy. When code reads admin.firestore, the proxy returns Proxied.getOriginal(target, "firestore"), which is:

return value.bind(target);

A bound function is a new function object, and it does not carry the original's own properties. So the statics on admin.firestoreFieldValue and the rest — are undefined inside the emulator. Checked directly:

original .FieldValue: function
bound    .FieldValue: undefined

It works normally in a plain node process and in deployed functions, which is why this went unnoticed until someone tried to run the pipeline locally.

The change

Import FieldValue from firebase-admin/firestore instead of reaching for it through the admin.firestore namespace. That avoids the proxy and behaves the same in both places.

src/chat/drain.ts already used the modular imports and explains why. This brings the rest of the directory in line with it.

All six call sites, in five files:

File Field
on-analysis-document-pending.ts docImaged
on-analysis-document-imaged.ts the comment's createdAt
on-analysis-document-imaged.ts the done record's completedAt
post-document-comment.ts createdAt
post-exemplar-comment.ts createdAt
on-class-data-doc-written.ts summaryCreatedAt

admin.firestore.FieldValue was the only namespace static used anywhere in functions-v2/src — no Timestamp, no FieldPath, no admin.database.ServerValue — so this covers all of it rather than part of it. on-class-data-doc-written.ts no longer imports firebase-admin at all, since that was its only use of it.

Testing

The existing suite passes, unchanged at 122 tests, along with typecheck and lint.

No test covers this change, and that is deliberate. The tests in functions-v2 call the functions directly rather than through the emulator's runtime, so firebase-admin is never proxied and they cannot tell the two forms apart. They confirm the change breaks nothing; they cannot confirm it fixes anything.

A check that read the source files and failed on the old form was written and then dropped. It could not tell a runtime call from a type annotation such as admin.firestore.Firestore, which TypeScript erases at compile time and which is perfectly safe.

What was verified by hand

All against the emulators, with the change applied.

The full AI analysis pipeline ran end to end through the CLUE user interface — a text-only document, a drawing-only document, an empty document, and the mock evaluator — each producing a comment in the panel. Before the change, every one of those runs stopped at the first serverTimestamp() call.

Posting a comment from the comments panel exercised post-document-comment. Calling the postExemplarComment endpoint directly exercised post-exemplar-comment. Both wrote a real createdAt timestamp.

on-class-data-doc-written was not run, because triggering it makes a paid OpenAI call. Its change is identical to the other five.

@codecov

codecov Bot commented Aug 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 86.38%. Comparing base (f225c2f) to head (bb2331c).

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2989      +/-   ##
==========================================
- Coverage   86.38%   86.38%   -0.01%     
==========================================
  Files         996      996              
  Lines       56858    56858              
  Branches    15060    15060              
==========================================
- Hits        49117    49115       -2     
- Misses       7721     7723       +2     
  Partials       20       20              
Flag Coverage Δ
cypress ?
cypress-regression 71.17% <ø> (+0.88%) ⬆️
cypress-smoke 41.21% <ø> (+0.05%) ⬆️
jest 57.76% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

The functions emulator replaces the firebase-admin module with a proxy.
Reading admin.firestore returns Proxied.getOriginal(target, "firestore"),
which is value.bind(target). A bound function is a new function object and
does not carry the original's own properties, so admin.firestore.FieldValue
is undefined in the emulator. It works normally in a plain node process and
in deployed functions, which is why this went unnoticed.

Any function that reached a serverTimestamp() call therefore failed with
"TypeError: Cannot read properties of undefined (reading 'serverTimestamp')".
The emulator stopped the function, nothing was recorded, and for the queue
functions the entry was left in place. That made the AI analysis pipeline
impossible to run end to end against the emulators.

Importing FieldValue from firebase-admin/firestore avoids the namespace and
behaves the same in both places. This changes all six call sites, in
on-analysis-document-pending, on-analysis-document-imaged,
post-document-comment, post-exemplar-comment and on-class-data-doc-written.
admin.firestore.FieldValue was the only namespace static used anywhere in
functions-v2/src, so nothing of this kind is left.
on-class-data-doc-written no longer imports firebase-admin, since that was
its only use of it.

Each changed file carries a one-line note saying why FieldValue is imported
separately, so the two imports of firebase-admin do not look like a mistake.
The full explanation is here rather than repeated in the code.

No test covers this. The tests in functions-v2 call the functions directly,
so firebase-admin is never proxied and they cannot tell the difference. A
check that read the source for the old form was considered and dropped: it
could not distinguish a runtime call from a type annotation such as
admin.firestore.Firestore, which is erased at compile time and safe, so it
would have failed on correct code.

Verified by hand against the emulator. With the change, the whole AI analysis
pipeline ran end to end through the CLUE user interface: a text-only document,
a drawing-only document, an empty document, and the mock evaluator, each
producing a comment. Posting a comment from the comments panel exercised
post-document-comment, and calling the postExemplarComment endpoint directly
exercised post-exemplar-comment. Both wrote a real createdAt timestamp.
on-class-data-doc-written was not run, because it makes a paid OpenAI call.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@emcelroy
emcelroy force-pushed the fix-fieldvalue-under-functions-emulator branch from adecdf2 to 541ad0f Compare August 29, 2026 14:31
@emcelroy
emcelroy requested a lite review from Copilot August 29, 2026 14:38
@cypress

cypress Bot commented Aug 29, 2026

Copy link
Copy Markdown

collaborative-learning    Run #20206

Run Properties:  status check passed Passed #20206  •  git commit bb2331c28d: Merge branch 'master' into fix-fieldvalue-under-functions-emulator
Project collaborative-learning
Branch Review fix-fieldvalue-under-functions-emulator
Run status status check passed Passed #20206
Run duration 10m 47s
Commit git commit bb2331c28d: Merge branch 'master' into fix-fieldvalue-under-functions-emulator
Committer Ethan McElroy
View all properties for this run ↗︎

Test results
Tests that failed  Failures 0
Tests that were flaky  Flaky 1
Tests that did not run due to a developer annotating a test with .skip  Pending 5
Tests that did not run due to a failure in a mocha hook  Skipped 0
Tests that passed  Passing 221
View all changes introduced in this branch ↗︎

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request fixes Firebase Functions emulator failures caused by admin.firestore being proxied/bound such that namespace statics (notably admin.firestore.FieldValue) become undefined. It updates affected functions to use the modular FieldValue import from firebase-admin/firestore, so serverTimestamp() works consistently in both emulator and deployed environments.

Changes:

  • Replaced admin.firestore.FieldValue.serverTimestamp() with FieldValue.serverTimestamp() at all identified call sites.
  • Added modular FieldValue imports (and removed the unused firebase-admin import where it was only needed for FieldValue).
  • Added inline comments explaining the emulator/proxy behavior motivating the modular import.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.

Show a summary per file
File Description
functions-v2/src/post-exemplar-comment.ts Uses modular FieldValue for createdAt timestamp when posting exemplar comments.
functions-v2/src/post-document-comment.ts Uses modular FieldValue for createdAt timestamp when posting document comments.
functions-v2/src/on-class-data-doc-written.ts Removes firebase-admin import and uses modular FieldValue for summaryCreatedAt.
functions-v2/src/on-analysis-document-pending.ts Uses modular FieldValue for docImaged timestamp when advancing queue docs.
functions-v2/src/on-analysis-document-imaged.ts Uses modular FieldValue for createdAt and completedAt timestamps when writing analysis results.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@emcelroy
emcelroy marked this pull request as ready for review August 29, 2026 14:45
@emcelroy
emcelroy requested a review from scytacki August 29, 2026 14:45

@scytacki scytacki left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me.

@emcelroy
emcelroy merged commit 68b6675 into master Sep 1, 2026
31 of 32 checks passed
@emcelroy
emcelroy deleted the fix-fieldvalue-under-functions-emulator branch September 1, 2026 21:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants