From 05afc49ea64c874e0ea296b5d5d4b49bb82c6d6e Mon Sep 17 00:00:00 2001
From: Paul Rastoin <45004772+prastoin@users.noreply.github.com>
Date: Fri, 10 Jul 2026 12:50:29 +0200
Subject: [PATCH] Fix front-component cache read rejection bypassing network
fallback (#22785)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## Summary
Quick follow-up to #22672.
`frontComponentCacheStorageService.read` returned
`cachedResponse.text()` without awaiting it, so the promise escaped the
surrounding `try/catch`. A cache entry with an unreadable body (corrupt
or partially-evicted `CacheStorage` entry) would reject in
`fetchComponentSource` and break component rendering entirely, instead
of being treated as a cache miss with a network fallback.
- `await` the body read inside the `try/catch` so decoding failures
degrade to a network fetch
- Add a regression test: cached body read rejects → source is still
served from the network
## Test plan
- `fetchComponentSource.spec.ts` — new test `falls back to the network
when the cached response body is unreadable`; full renderer suite passes
(14 tests)
Made with [Cursor](https://cursor.com)
---------
Co-authored-by: Cursor
---
.../__tests__/fetchComponentSource.spec.ts | 23 +++++++++++++++++++
.../frontComponentCacheStorageService.ts | 4 +++-
2 files changed, 26 insertions(+), 1 deletion(-)
diff --git a/packages/twenty-front-component-renderer/src/remote/worker/utils/__tests__/fetchComponentSource.spec.ts b/packages/twenty-front-component-renderer/src/remote/worker/utils/__tests__/fetchComponentSource.spec.ts
index ff3ca7e9c2..66ca456345 100644
--- a/packages/twenty-front-component-renderer/src/remote/worker/utils/__tests__/fetchComponentSource.spec.ts
+++ b/packages/twenty-front-component-renderer/src/remote/worker/utils/__tests__/fetchComponentSource.spec.ts
@@ -161,6 +161,29 @@ describe('fetchComponentSource', () => {
expect(fetchMock).toHaveBeenCalledTimes(1);
});
+ it('falls back to the network when the cached response body is unreadable', async () => {
+ const cache = new FakeCache();
+
+ setupCaches(cache);
+
+ cache.match.mockResolvedValueOnce({
+ text: async () => {
+ throw new Error('body unreadable');
+ },
+ });
+
+ const fetchMock = jest.fn(async () =>
+ createFakeJsResponse(COMPONENT_SOURCE),
+ );
+
+ globalThis.fetch = fetchMock as unknown as typeof fetch;
+
+ const source = await fetchComponentSource({ url: FINGERPRINTED_URL });
+
+ expect(source).toBe(COMPONENT_SOURCE);
+ expect(fetchMock).toHaveBeenCalledTimes(1);
+ });
+
it('does not cache a network response whose checksum does not match the URL', async () => {
const cache = new FakeCache();
diff --git a/packages/twenty-front-component-renderer/src/remote/worker/utils/frontComponentCacheStorageService.ts b/packages/twenty-front-component-renderer/src/remote/worker/utils/frontComponentCacheStorageService.ts
index 8884cfb06d..242e976557 100644
--- a/packages/twenty-front-component-renderer/src/remote/worker/utils/frontComponentCacheStorageService.ts
+++ b/packages/twenty-front-component-renderer/src/remote/worker/utils/frontComponentCacheStorageService.ts
@@ -48,7 +48,9 @@ export const frontComponentCacheStorageService = {
try {
const cachedResponse = await cache.match(url);
- return isDefined(cachedResponse) ? cachedResponse.text() : undefined;
+ return isDefined(cachedResponse)
+ ? await cachedResponse.text()
+ : undefined;
} catch {
return undefined;
}