You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Yes, this behavior used to work in the previous version
The previous version in which this bug was not present was
22.0.9
Description
Description
Since #33321 / PR #33519 ("Share persistent build cache across git worktrees"), @angular/build resolves the default persistent cache to the common git directory — i.e. the main repository root — for every git worktree.
For plain ng build this works as intended. But for an SSR app, ng serve also writes vite's SSR prebundles into that cache, at <main-repo>/.angular/cache/<version>/<project>/vite/deps_ssr/. Those prebundled files then contain bare imports (e.g. firebase-admin/data-connect) that Node resolves relative to the prebundle's own location — under the main repo. The worktree's node_modules is never consulted, so the import fails and every SSR request returns HTTP 500.
The result is that ng serve is unusable in any git worktree of an SSR project from 22.1.0 onward.
Two things worth emphasising, because they are easy to assume otherwise:
This is not a concurrency problem. It is not about two worktrees building at the same time. A single worktree, with the shared cache freshly deleted and nothing else running, still fails on the very first request.
ng build is unaffected, which makes this easy to miss in CI — only the dev-server SSR path breaks.
This is not a request to revert #33321. Sharing the cache is reasonable and the disk-space motivation there is real. The problem is specifically that deps_ssr is not relocatable the way the rest of the cache is: its contents encode a module-resolution root, so placing it outside the worktree breaks resolution.
Suggested fix
Keep the shared cache, but keep the vite deps_ssr (and likely deps) prebundles worktree-local, since they are position-dependent in a way the other cache entries are not. Alternatively, resolve SSR externals against the workspace root rather than the prebundle location.
A workaround exists but is not committable: setting an absolutecli.cache.path in angular.json short-circuits the git lookup and fixes it. It has to be absolute (a relative path is still redirected to the common git dir), so it is machine-specific and cannot be checked into a shared repo. There is also no environment variable for the cache path (NG_BUILD_CACHE_STORE only selects the lmdb/sqlite backend), so there is currently no portable way for a team to opt out.
Minimal Reproduction
An SSR app with a server-side dependency listed in externalDependencies — i.e. left unbundled, so it stays a bare import inside the SSR prebundle and has to be resolved from disk at request time. That is the condition that makes resolution sensitive to where the prebundle lives. Reproduced with firebase-admin, but the mechanism is not specific to it.
# 1. Create an SSR app in a git repo
ng new my-ssr-app --ssr
cd my-ssr-app
git init && git add -A && git commit -m "init"# 2. Add a server-side dependency, imported from the SSR entry
npm i firebase-admin
# in src/server.ts: import 'firebase-admin/app';# 3. Mark it external so it is not bundled (angular.json, build options):# "externalDependencies": ["firebase-admin", "firebase-admin/app"]
git add -A && git commit -m "add external server dep"# 4. Create a worktree and install there
git worktree add ../wt-test -b wt-test
cd ../wt-test
npm i
# 5. Serve from the worktree
ng serve
# open http://localhost:4200 -> HTTP 500
Observed: the prebundle is written to <main-repo>/.angular/cache/<version>/<project>/vite/deps_ssr/, not to ../wt-test/.angular/cache/..., and the SSR request fails.
Expected: the app renders, as it does on 22.0.9.
The failure is most visible with a package manager that does not flatten node_modules (this was found under pnpm). With npm's hoisted layout a stray parent-directory node_modules may accidentally satisfy the lookup and mask the bug, so pnpm is the more reliable way to observe it.
To confirm the cause without a full reproduction, the resolution logic can be exercised directly:
Note that on 22.1.x the worktree resolves to the repository root, while a non-worktree checkout of the same project resolves to apps/my-app/.angular/cache — so the two are not merely shared, they are different directories.
Exception or Error
Error: Cannot find module 'firebase-admin/data-connect' imported from
'/Users/me/Workspace/myrepo/.angular/cache/22.1.6/myapp/vite/deps_ssr/@scope_my-admin-sdk.js'
at fetchModule (file:///Users/me/Workspace/myrepo/.claude/worktrees/my-worktree/node_modules/.pnpm/vite@8.1.5/node_modules/vite/...)
# Note the two different roots in that single error: the failing import lives under the **main repo** (`/myrepo/.angular/cache/...`), while vite itself is running from the **worktree** (`/myrepo/.../my-worktree/node_modules/...`).
Your Environment
@angular/build: 22.1.6 (runtime-verified); the responsible code is present in 22.1.0+
@angular/cli: 22.1.6
@angular/core: 22.1.4
@angular/ssr: 22.1.6
Node: 22.23.1
Package Manager: pnpm 11.15.1
OS: macOS 26.6.2 (darwin arm64)
# Verified not present on `@angular/build` 22.0.9. The `getCacheBasePath()` helper that introduces the git-worktree lookup first appears in `src/utils/normalize-cache.js` in 22.1.0.
Bisected to the package level by diffing the shipped src/utils/normalize-cache.js between 22.0.9 (resolve(workspaceRoot, path), no git logic) and 22.1.0+ (getCacheBasePath(), which reads .git → gitdir: → commondir and returns resolve(dirname(commonGitDir), cachePathSetting)).
A/B tested on one machine with the same commit and the same 22.1.6 install, changing only cli.cache.path:
cli.cache.path
cache location
ng serve SSR
default
<main-repo>/.angular/cache/22.1.6
HTTP 500
absolute, worktree-local
worktree's own .angular/cache/22.1.6
HTTP 200, correct SSR output
Impact for monorepos that use worktrees per feature branch: local dev and any Playwright/e2e suite that needs a dev server are both blocked, so the only options today are pinning @angular/build / @angular/cli / @angular/ssr to ~22.0.9, or hand-writing a machine-specific absolute path into angular.json on every developer's machine.
Command
serve
Is this a regression?
The previous version in which this bug was not present was
22.0.9
Description
Description
Since #33321 / PR #33519 ("Share persistent build cache across git worktrees"),
@angular/buildresolves the default persistent cache to the common git directory — i.e. the main repository root — for every git worktree.For plain
ng buildthis works as intended. But for an SSR app,ng servealso writes vite's SSR prebundles into that cache, at<main-repo>/.angular/cache/<version>/<project>/vite/deps_ssr/. Those prebundled files then contain bare imports (e.g.firebase-admin/data-connect) that Node resolves relative to the prebundle's own location — under the main repo. The worktree'snode_modulesis never consulted, so the import fails and every SSR request returns HTTP 500.The result is that
ng serveis unusable in any git worktree of an SSR project from 22.1.0 onward.Two things worth emphasising, because they are easy to assume otherwise:
ng buildis unaffected, which makes this easy to miss in CI — only the dev-server SSR path breaks.This is not a request to revert #33321. Sharing the cache is reasonable and the disk-space motivation there is real. The problem is specifically that
deps_ssris not relocatable the way the rest of the cache is: its contents encode a module-resolution root, so placing it outside the worktree breaks resolution.Suggested fix
Keep the shared cache, but keep the vite
deps_ssr(and likelydeps) prebundles worktree-local, since they are position-dependent in a way the other cache entries are not. Alternatively, resolve SSR externals against the workspace root rather than the prebundle location.A workaround exists but is not committable: setting an absolute
cli.cache.pathinangular.jsonshort-circuits the git lookup and fixes it. It has to be absolute (a relative path is still redirected to the common git dir), so it is machine-specific and cannot be checked into a shared repo. There is also no environment variable for the cache path (NG_BUILD_CACHE_STOREonly selects the lmdb/sqlite backend), so there is currently no portable way for a team to opt out.Minimal Reproduction
An SSR app with a server-side dependency listed in
externalDependencies— i.e. left unbundled, so it stays a bare import inside the SSR prebundle and has to be resolved from disk at request time. That is the condition that makes resolution sensitive to where the prebundle lives. Reproduced withfirebase-admin, but the mechanism is not specific to it.Observed: the prebundle is written to
<main-repo>/.angular/cache/<version>/<project>/vite/deps_ssr/, not to../wt-test/.angular/cache/..., and the SSR request fails.Expected: the app renders, as it does on 22.0.9.
The failure is most visible with a package manager that does not flatten
node_modules(this was found under pnpm). With npm's hoisted layout a stray parent-directorynode_modulesmay accidentally satisfy the lookup and mask the bug, so pnpm is the more reliable way to observe it.To confirm the cause without a full reproduction, the resolution logic can be exercised directly:
Note that on 22.1.x the worktree resolves to the repository root, while a non-worktree checkout of the same project resolves to
apps/my-app/.angular/cache— so the two are not merely shared, they are different directories.Exception or Error
Your Environment
Anything else relevant?
Introduced by Share persistent build cache across git worktrees #33321 → PR feat(@angular/build): share persistent build cache across git worktrees #33519 (merged 2026-07-07).
Bisected to the package level by diffing the shipped
src/utils/normalize-cache.jsbetween 22.0.9 (resolve(workspaceRoot, path), no git logic) and 22.1.0+ (getCacheBasePath(), which reads.git→gitdir:→commondirand returnsresolve(dirname(commonGitDir), cachePathSetting)).A/B tested on one machine with the same commit and the same 22.1.6 install, changing only
cli.cache.path:cli.cache.pathng serveSSR<main-repo>/.angular/cache/22.1.6.angular/cache/22.1.6Impact for monorepos that use worktrees per feature branch: local dev and any Playwright/e2e suite that needs a dev server are both blocked, so the only options today are pinning
@angular/build/@angular/cli/@angular/ssrto~22.0.9, or hand-writing a machine-specific absolute path intoangular.jsonon every developer's machine.