The story of Jest hanging in CI, and the story of migrating to Floci because LocalStack became paid

The story of Jest hanging in CI, and the story of migrating to Floci because LocalStack became paid

This is a record of investigating an issue where Jest hangs after all tests pass in GitHub Actions, and of migrating to Floci under the MIT license following the discontinuation of the LocalStack community edition.
2026.07.12

This page has been translated by machine translation. View original

Introduction

"All tests are passing, but CI won't finish."

Have you ever experienced this? I ran into exactly this issue recently. On GitHub Actions, after all 44 server tests (Jest) passed, the Jest process would not exit and the job kept hanging.

While investigating, I tried to reproduce the issue locally and discovered that LocalStack had discontinued its community edition in March 2026. Searching for an alternative tool, I came across Floci.

This article summarizes the entire investigation and the steps taken to address it.

The Problem of Jest Hanging in CI

jest-open-handles-localstack-floci-debug-flow

Symptoms

Looking at the GitHub Actions logs, the following message appeared.

Tests:       44 passed, 44 total
Ran all test suites.

Jest did not exit one second after the test run has completed.

'This usually means that there are asynchronous operations that weren't stopped
in your tests. Consider running Jest with `--detectOpenHandles` to troubleshoot
this issue.'

All tests passed. However, the Jest process would not exit, and the job kept waiting for a timeout or cancellation without executing subsequent steps.

Cause: Open Handles

When Jest cannot exit, the cause is usually asynchronous connections or timers that have not been closed somewhere. Common examples include:

  • AWS SDK clients (DynamoDB / S3, etc.) holding open connections
  • HTTP servers that have not been close()d
  • setInterval / setTimeout that have not been cleared with clearInterval / clearTimeout

Investigation: --detectOpenHandles

Jest has an option for identifying the cause.

jest --detectOpenHandles

Running with this option outputs which handles remain open, along with a stack trace showing which file and line they were created on.

● Jest has detected the following 1 open handle potentially keeping Jest from exiting:

  ● TCPWRAP

      17 |   const client = new DynamoDBClient({ region: "ap-northeast-1" });
         |                  ^

This makes it immediately clear which AWS SDK client is the cause.

Temporary Fix: --forceExit

Before applying a root-cause fix (such as calling destroy() on clients in afterAll), you can use --forceExit as a temporary measure to keep CI from stopping.

Simply add it to the test script in package.json.

{
  "scripts": {
    "test": "jest --runInBand --forceExit"
  }
}

--forceExit forcibly terminates Jest after the tests complete. Open handles will remain, but CI will no longer hang. Combining it with --detectOpenHandles allows you to output diagnostic information to CI logs while still exiting normally.

"test": "jest --runInBand --detectOpenHandles --forceExit"

Key Point: When the Issue Does Not Reproduce Locally

jest-open-handles-localstack-floci-ci-vs-local

In this case, the issue did not reproduce locally even with --detectOpenHandles, and Jest exited normally.

The reason is that locally, a mock tool (Floci, described later) is used to emulate AWS, so all AWS API calls are contained locally. In CI, it is believed that connections to actual AWS services (such as Kendra) remained open, causing the hang.

In this situation, a practical approach is to use --forceExit to allow CI to exit normally while using the --detectOpenHandles logs to identify the root cause.

LocalStack Had Become a Paid Service

When I tried to reproduce the issue locally, I hit a wall when attempting to use LocalStack.

What Happened

License activation failed! 🔑❌

Reason: No credentials were found in the environment.
Please make sure to either set the LOCALSTACK_AUTH_TOKEN variable to a valid auth token.

Previously, it should have been possible to start with just docker run localstack/localstack, but now it requires an auth token and will not start.

Discontinuation of the LocalStack Community Edition

Upon investigation, it turned out that LocalStack discontinued its community edition (free, no license required) on March 23, 2026.

Period Status
Up to March 2026 The localstack/localstack image was available for free
After March 2026 An auth token is required for all images

An image of older versions called localstack/localstack:community-archive exists, but commercial use requires separate verification. The free tier for free accounts is explicitly stated to be for "personal, non-commercial" use.

For use in business projects, you either need to obtain a commercial license or migrate to an alternative tool.

Floci: A LocalStack-Compatible Tool with MIT License

When searching for a LocalStack alternative, Floci emerged as a strong candidate.

What is Floci?

  • MIT license (commercial use allowed, free)
  • Drop-in replacement for LocalStack Community (same port 4566, same credential format)
  • Emulates 50+ AWS services (DynamoDB, S3, SQS, Lambda, etc.)
  • No auth token required

Since it uses the same endpoint as LocalStack (http://localhost:4566), you can switch over without changing any existing code.

Setup Instructions

1. Start Floci

docker run -d -p 4566:4566 floci/floci

Verify it is running:

curl -s http://localhost:4566/_localstack/health

You are good to go if "dynamodb":"running" and "s3":"running" are displayed.

2. Set Up AWS Resources

Since Floci does not persist data, you need to create tables and buckets every time it starts. If you have an existing setup script for LocalStack, you can use it as-is.

It works simply by setting dummy AWS credentials (Floci does not validate credentials).

AWS_ACCESS_KEY_ID=test AWS_SECRET_ACCESS_KEY=test node scripts/setup.js

3. Run the Tests

AWS_ACCESS_KEY_ID=test AWS_SECRET_ACCESS_KEY=test jest

If you are using Colima instead of Docker Desktop, start Colima first.

colima start
docker run -d -p 4566:4566 floci/floci

Comparison with LocalStack

Item LocalStack (after 2026) Floci
License Paid plan required for commercial use MIT (commercial use allowed)
Auth token Required Not required
Port 4566 4566 (compatible)
Number of supported services Many (expanded with Pro plan) 50+
Startup speed Somewhat slow Fast (24ms)

Summary

To summarize the actions taken:

  1. Jest hanging → Identify the cause with --detectOpenHandles, apply a temporary fix with --forceExit. If it does not reproduce locally, the real AWS connections in CI may be the cause.
  2. LocalStack becoming paid → After March 2026, business use requires a paid plan or an alternative tool.
  3. Migration to Floci → MIT license, LocalStack-compatible, no auth token required, and can be used as a drop-in replacement.

"CI hanging only in CI but not locally" is a tricky situation, but adding --detectOpenHandles --forceExit to CI and reviewing the logs is a reliable, if methodical, way to investigate. Regarding LocalStack's move to paid plans, alternatives like Floci are increasingly available, so I recommend choosing a tool that fits your licensing requirements.

Share this article