CIでJestがハングした話と、LocalStackが有料化したのでFlociに移行した話

CIでJestがハングした話と、LocalStackが有料化したのでFlociに移行した話

GitHub ActionsでJestがテスト全件パス後にハングする問題の調査と、LocalStackコミュニティ版廃止に伴いMITライセンスのFlociへ移行した記録です。
2026.07.12

はじめに

「テストは全部パスしてるのに、CI が終わらない」

こんな経験はありませんか? 先日まさにこれに遭遇しました。GitHub Actions 上でサーバーのテスト(Jest)が 44件すべてパスした後、Jest プロセスが終了せずにジョブがハングし続けるという問題です。

調査を進める中で、ローカルで再現しようとしたところ、LocalStack が 2026年3月にコミュニティ版を廃止していたことも発覚。代替ツールを探して Floci にたどり着きました。

この記事では、その一連の調査と対応をまとめます。

CIでJestがハングする問題

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

症状

GitHub Actions のログを見ると、こんなメッセージが出ていました。

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.'

テスト自体は全件パス。しかし Jest プロセスが終了せず、後続ステップが実行されないままジョブがタイムアウトかキャンセルを待ち続けます。

原因:オープンハンドル

Jest がプロセスを終了できない場合、多くはどこかで非同期の接続やタイマーが閉じられていないのが原因です。よくある例:

  • AWS SDK クライアント(DynamoDB / S3 など)が接続を保持したまま
  • HTTP サーバーが close() されていない
  • setInterval / setTimeoutclearInterval / clearTimeout されていない

調査:--detectOpenHandles

Jest には原因を特定するオプションがあります。

jest --detectOpenHandles

これをつけて実行すると、どのハンドルが残っているか、どのファイル・どの行で生成されたかをスタックトレース付きで出力してくれます。

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

  ● TCPWRAP

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

このように、どの AWS SDK クライアントが原因かが一目でわかります。

暫定対応:--forceExit

根本原因の修正(afterAll でクライアントを destroy() する等)を進める前に、CI を止めないための暫定対応として --forceExit が使えます。

package.json のテストスクリプトに追加するだけです。

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

--forceExit はテスト完了後に Jest を強制終了させます。オープンハンドルは残りますが、CI がハングしなくなります。--detectOpenHandles と併用すると、CI ログに診断情報を出しながら正常終了させることもできます。

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

ポイント:ローカルで再現しない場合

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

今回、ローカルでは --detectOpenHandles をつけても問題が再現せず、Jest が正常終了しました。

理由は、ローカルでは AWS をエミュレートするモックツール(後述の Floci)が使われており、すべての AWS API コールがローカルに閉じているためです。CI では実際の AWS サービス(Kendra 等)への接続が残り、ハングしたと考えられます。

この場合、--forceExit で CI を正常終了させつつ、--detectOpenHandles のログで原因箇所を特定するアプローチが現実的です。

LocalStack が有料化していた

ローカルでの再現を試みようとしたとき、LocalStack を使おうとして壁にぶつかりました。

何が起きたか

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.

以前は docker run localstack/localstack だけで起動できていたはずが、auth token を要求されて起動できません。

LocalStack コミュニティ版の廃止

調べると、LocalStack は 2026年3月23日にコミュニティ版(無料・ライセンス不要)を廃止していました。

時期 状況
~2026年3月 localstack/localstack イメージが無料で使用可能
2026年3月以降 すべてのイメージで auth token が必須に

localstack/localstack:community-archive という過去バージョンのイメージも存在しますが、商用利用については別途確認が必要です。無料アカウントの free tier は「個人・非商用」向けと明示されています。

業務プロジェクトで使う場合は、商用ライセンスの取得か、代替ツールへの移行が必要です。

Floci:MIT ライセンスの LocalStack 互換ツール

LocalStack の代替を探したところ、Floci が有力候補でした。

Floci とは

  • MIT ライセンス(商用利用可、無料)
  • LocalStack Community のドロップイン代替(同じポート 4566、同じ認証情報形式)
  • 50以上の AWS サービスをエミュレート(DynamoDB, S3, SQS, Lambda 等)
  • auth token 不要

LocalStack と同じエンドポイント(http://localhost:4566)を使うため、既存のコードを変更せずにそのまま切り替えられます。

セットアップ手順

1. Floci を起動する

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

起動確認:

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

"dynamodb":"running""s3":"running" が表示されれば OK です。

2. AWS リソースをセットアップする

Floci はデータを永続化しないため、起動のたびにテーブルやバケットを作成する必要があります。既存の LocalStack 用セットアップスクリプトがあれば、そのまま使えます

ダミーの AWS 認証情報を設定するだけで動作します(Floci は認証情報を検証しません)。

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

3. テストを実行する

AWS_ACCESS_KEY_ID=test AWS_SECRET_ACCESS_KEY=test jest

Docker Desktop の代わりに Colima を使っている場合、先に Colima を起動します。

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

LocalStack との比較

項目 LocalStack(2026年以降) Floci
ライセンス 商用利用には有料プラン必須 MIT(商用利用可)
auth token 必要 不要
ポート 4566 4566(互換)
対応サービス数 多数(Pro プランで拡張) 50以上
起動速度 やや遅い 速い(24ms)

まとめ

今回の対応をまとめると:

  1. Jest のハング--detectOpenHandles で原因特定、--forceExit で暫定対応。ローカルで再現しない場合は CI の実 AWS 接続が原因の可能性がある
  2. LocalStack の有料化 → 2026年3月以降、業務利用では有料プランか代替ツールが必要
  3. Floci への移行 → MIT ライセンス、LocalStack 互換、auth token 不要でドロップイン置き換え可能

「ローカルで再現しないのにCIだけハングする」は厄介ですが、--detectOpenHandles --forceExit を CI に仕込んでログを見るのが地道ながら確実な調査手段です。LocalStack 有料化については、Floci のような代替ツールの選択肢が広がっているので、ライセンス要件に合ったツールを選ぶとよいと思います。

この記事をシェアする

関連記事