From 1b4d853f866c72961317b3c52cb7a21770f054cf Mon Sep 17 00:00:00 2001 From: Juan Ibiapina Date: Mon, 22 Jun 2026 11:18:59 +0200 Subject: [PATCH 1/2] fix: match NOT_LISTENING_ERROR against local-dev runtime message The isNotListeningError helper uses .includes() to detect the error. In production the runtime returns 'The container is not listening in the TCP address ...', but in local dev (wrangler dev) it returns 'container is not listening ...' without the leading 'the'. The current constant 'the container is not listening' is not a substring of the local-dev variant, so the readiness loop never recognises the error and retries indefinitely. Shorten the constant to 'container is not listening' so it matches both variants. --- .changeset/fix-not-listening-error.md | 5 +++++ src/lib/container.ts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/fix-not-listening-error.md diff --git a/.changeset/fix-not-listening-error.md b/.changeset/fix-not-listening-error.md new file mode 100644 index 0000000..71a47c0 --- /dev/null +++ b/.changeset/fix-not-listening-error.md @@ -0,0 +1,5 @@ +--- +"@cloudflare/containers": patch +--- + +Fix NOT_LISTENING_ERROR constant to match local-dev runtime error message diff --git a/src/lib/container.ts b/src/lib/container.ts index b10439d..8d2d61a 100644 --- a/src/lib/container.ts +++ b/src/lib/container.ts @@ -24,7 +24,7 @@ const NO_CONTAINER_INSTANCE_ERROR = const RATE_LIMITED_ERROR = 'you are requesting too many containers per second'; const RUNTIME_SIGNALLED_ERROR = 'runtime signalled the container to exit:'; const UNEXPECTED_EXIT_ERROR = 'container exited with unexpected exit code:'; -const NOT_LISTENING_ERROR = 'the container is not listening'; +const NOT_LISTENING_ERROR = 'container is not listening'; const CONTAINER_STATE_KEY = '__CF_CONTAINER_STATE'; const OUTBOUND_CONFIGURATION_KEY = 'OUTBOUND_CONFIGURATION'; From f8999cc9689c0a1e0ef468e85236b65301645f48 Mon Sep 17 00:00:00 2001 From: Juan Ibiapina Date: Mon, 22 Jun 2026 11:34:48 +0200 Subject: [PATCH 2/2] test: verify local-dev 'not listening' error is recognised Add a unit test that mocks the local-dev error message (without the leading 'the') and asserts doStartContainer returns immediately instead of retrying. The test counts fetch calls: 2 with the fix (recognised on first try), 3 without (falls through to retry). --- src/tests/container.test.ts | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/tests/container.test.ts b/src/tests/container.test.ts index c2e603c..f87b801 100644 --- a/src/tests/container.test.ts +++ b/src/tests/container.test.ts @@ -74,6 +74,31 @@ describe('Container', () => { expect(mockCtx.container.getTcpPort).toHaveBeenCalledWith(8080); }); + test('startAndWaitForPorts should recognise local-dev "not listening" error without leading "the"', async ({ + mockCtx, + container, + }) => { + // In local dev (wrangler dev) the runtime returns "container is not listening" + // without the leading "the" that production uses. Both variants must be + // recognised so doStartContainer treats the state as benign and returns + // instead of retrying until timeout. + const localDevError = new Error('container is not listening on TCP address 10.0.0.1:8080'); + const fetchMock = vi + .fn() + .mockRejectedValueOnce(localDevError) + .mockResolvedValue(new Response('ok')); + mockCtx.container.getTcpPort.mockReturnValue({ fetch: fetchMock }); + + await container.startAndWaitForPorts(8080); + + expect(mockCtx.container.getTcpPort).toHaveBeenCalledWith(8080); + // Exactly 2 calls: doStartContainer recognises the error and returns + // immediately (1 call), then waitForPort succeeds on its first attempt + // (1 call). Without the fix the error is not recognised, doStartContainer + // retries, and the count rises to 3+. + expect(fetchMock).toHaveBeenCalledTimes(2); + }); + test('startAndWaitForPorts should surface rate-limited startup errors on the final retry', async ({ mockCtx, container,