Skip to main content

Loading…

Skip to main content
HomeProjectsPostsResourcesContact
Justin Tsugranes LogoJustin Tsugranes Logo

Justin Tsugranes

HomeProjectsPostsResourcesContact

Stay in the loop

Occasional notes on what I'm building, lessons earned, and the studio behind it.

By subscribing, you agree to receive No spam. Unsubscribe in one click anytime. from Justin Tsugranes. No spam. Unsubscribe anytime. Privacy Policy

© 2026 Total Ventures LLC. All rights reserved.

Privacy PolicyTerms of ServiceCookie Policy
The Hidden Monorepo Bug: Stale Firebase Functions | Justin Tsugranes | Justin Tsugranes
Xinf

The Hidden Monorepo Bug: Stale Firebase Functions

You've fixed a shared package, type-checked, and passed CI, yet your Firebase function ships stale. The bug isn't in your code, but in the packaging step. Learn how to diagnose and fix this common monorepo deployment issue.

Justin Tsugranes·July 7, 2026·4 min read
On this page
  1. The Illusion of Freshness: Why Your Code Appears Current
  2. The Core Problem: Stale .tgz Archives
  3. The Deployment Gap
  4. Diagnosing the Stale Package
  5. The Fix: Forcing .tgz Regeneration
  6. 1. Explicit npm pack or yarn pack
  7. 2. Using Monorepo Tools (e.g., Lerna, Nx, Turborepo)
  8. 3. Custom Build Script
  9. Preventing Future Occurrences

If you're deploying Firebase functions from a monorepo, you might have encountered a particularly frustrating bug: a shared package fix lands, type-checks, and passes CI, but the deployed function still runs the old, stale code. The problem isn't in your code or your tests; it's a subtle interaction with how Firebase functions bundle dependencies, specifically when those dependencies are local packages within your monorepo.

The Illusion of Freshness: Why Your Code Appears Current

When you're working in a monorepo, it's natural to expect that once you update a shared utility package and your CI pipeline passes, those changes will be reflected everywhere that package is used. For many parts of a modern application, this assumption holds true. Your local development environment picks up the changes, your tests run against the new code, and your type-checker confirms everything is in order.

However, Firebase Cloud Functions introduce a wrinkle. Unlike other deployment targets that might build directly from your source or a pre-compiled artifact, Firebase functions, particularly when dealing with local file: dependencies in package.json, have a specific bundling mechanism. They package each local dependency as a .tgz archive. This archive is then included in the final function bundle that gets uploaded to Google Cloud.

The Core Problem: Stale .tgz Archives

The critical detail here is that these .tgz archives are generated at a specific point in time. If your build or deploy process doesn't explicitly regenerate these archives before the Firebase function deployment step, you're effectively deploying an older version of your shared package, even if your local node_modules and package-lock.json are perfectly up-to-date.

Consider a typical monorepo-portfolio setup:

`` /monorepo /packages /shared-utils package.json index.ts /firebase-functions package.json index.ts package.json ``

In firebase-functions/package.json, you might have a dependency like:

``json "dependencies": { "@totalventures/shared-utils": "file:../shared-utils" } ``

When you run npm install or yarn install at the monorepo root, your package manager resolves this file: dependency by creating a .tgz archive of shared-utils and placing it in your node_modules or a similar cache. This is usually a one-time operation unless the shared-utils package.json version changes or you force a reinstall.

The Deployment Gap

Here's where the gap appears. You make a change in shared-utils/index.ts. Your local environment sees it. Your tests pass. You commit. Your CI runs npm install (which might not regenerate the .tgz if the version hasn't changed) and then firebase deploy. The firebase deploy command then bundles your function, including the existing .tgz for shared-utils that was generated earlier. If that .tgz wasn't updated, your function ships with the old code.

This is a subtle bug because all the usual signals of a successful build — passing tests, no type errors, a clean CI run — tell you everything is fine. The problem is hidden in the packaging step, not the code itself.

Diagnosing the Stale Package

To confirm this is your issue, you can inspect the deployed function's source code. Google Cloud Console allows you to view the source of your deployed functions. Look for the node_modules directory within your function's bundle and specifically examine the contents of your shared package. You'll likely find the older version of your code.

Another diagnostic step is to add a console log with a version number or a unique identifier from your shared package. Deploy the function and check the logs. If the log shows the old identifier, you've found your culprit.

The Fix: Forcing .tgz Regeneration

The solution involves ensuring that the .tgz archive for your local file: dependencies is always fresh before firebase deploy runs. There are a few ways to achieve this, depending on your monorepo tool and CI setup.

1. Explicit npm pack or yarn pack

The most direct method is to explicitly pack your shared package before deploying. In your Firebase function's package.json scripts, or in your CI pipeline, you can add a step:

```bash

In your CI script, before firebase deploy

cd packages/shared-utils npm pack cd ../firebase-functions npm install # This will now pick up the newly generated .tgz firebase deploy ```

This ensures that shared-utils.tgz is regenerated, and then npm install in your function's directory will use that fresh archive.

2. Using Monorepo Tools (e.g., Lerna, Nx, Turborepo)

Modern monorepo tools often have better ways to handle inter-package dependencies and caching. While they might not directly solve the .tgz issue for file: dependencies, they can help orchestrate the build process more effectively.

For example, with Lerna, you might use lerna publish --force-publish or lerna version to update package versions and then lerna bootstrap to ensure all dependencies are linked correctly. However, if you're sticking with file: dependencies for simplicity or specific reasons, the explicit npm pack approach is often more reliable for Firebase functions.

3. Custom Build Script

You can create a custom build script that orchestrates the entire process. This script would:

  1. Navigate to your shared package.
  2. Run npm pack (or yarn pack).
  3. Navigate to your Firebase function directory.
  4. Run npm install (to pick up the new .tgz).
  5. Run firebase deploy.

This gives you fine-grained control over the deployment flow and ensures the correct sequence of operations. This is particularly useful for a monorepo-portfolio where you need precise control over each service's deployment.

Preventing Future Occurrences

Once you've implemented a fix, it's crucial to integrate it into your CI/CD pipeline. Make sure that every time you deploy a Firebase function that depends on a local file: package, the .tgz for that package is regenerated. This might mean adjusting your firebase.json configuration or your CI script to include these pre-deployment steps.

This issue highlights the importance of understanding the specific bundling and deployment mechanisms of each platform, especially when working with complex monorepo-portfolio structures. A passing type-check is a good signal, but it doesn't always guarantee the bits on the server are the ones you intended.

What other subtle deployment issues have you encountered in monorepos?

RecommendedFree

Free download

Get the Launch Checklist →
If this resonated

Here's where the rest of it lives.

The free guides, the playbook, and a done-with-you sprint — the whole ladder, on one page.

See the resources
  • Free guides

    The Studio Launch Checklist + four operator guides, free.

  • Builder's Playbook

    Six reports with the architecture, funnel, email, and money systems — the decisions already made.

  • Solo Launch Sprint

    A fixed-scope, done-with-you sprint — walk out with it decided, sequenced, and live.

Studio Notes

How I’m building the studio.

The operator’s log — systems, decisions, and what’s working.

JT

Written by

Justin Tsugranes

Founder, Total Ventures

Solo-founder building a multi-brand product studio with AI agents. Writing about building, operating, and shipping.

ShareXLinkedInFacebook
#monorepo-portfolio#firebase functions stale#cloud functions deployment bug#monorepo shared package#firebase .tgz issue

On this page

  1. The Illusion of Freshness: Why Your Code Appears Current
  2. The Core Problem: Stale .tgz Archives
  3. The Deployment Gap
  4. Diagnosing the Stale Package
  5. The Fix: Forcing .tgz Regeneration
  6. 1. Explicit npm pack or yarn pack
  7. 2. Using Monorepo Tools (e.g., Lerna, Nx, Turborepo)
  8. 3. Custom Build Script
  9. Preventing Future Occurrences