Claude Codeでリアルタイム会議要約システムを構築した記録
即録くん(リアルタイム文字起こしアプリ)が出力するテキストログを、Claude Codeが3分おきに読み取って要約し、Nuxtアプリに表示する仕組みを作った。保険営業のロールプレイ(約45分)を題材にテストし、会議の進行に追従して要約が更新されていく動作を確認できた。
全体構成
即録くん(Windows) → テキストログ → Claude Code(devcontainer) → minutes.json → Nuxtアプリ(3秒ポーリング)
Claude Codeの /loop 3m /summarize-meeting コマンドでCronCreateを使い、3分間隔の自動実行を設定する。summarize-meetingスキルがログファイルを読み取り、要約をJSONに書き出す。NuxtアプリがそのJSONを3秒ごとにフェッチして画面に反映する。
devcontainerとWindowsのパス問題
最初に手が止まった。即録くんはWindows側で動いており、ログファイルはWindowsのファイルシステムに書き出される。一方、Claude Codeはdevcontainer(Linux)の中で動いている。そもそもログファイルを読めるのか。
発見: /workspace/logs/ からアクセスできた
devcontainerのマウント設定を確認すると、Windowsのプロジェクトディレクトリが /workspace/ にマウントされていた。即録くんのログ出力先をこのディレクトリ配下に設定すれば、devcontainer内から /workspace/logs/ のパスで読み取れる。
実際に ls /workspace/logs/ を実行し、即録くんが書き出したログファイルが見えることを確認した。
環境判定ロジックの追加
問題は、summarize-meetingスキルがWindows環境でもdevcontainer環境でも動く必要があること。ログファイルのパスが環境によって異なるため、実行環境を自動判定するロジックを組み込んだ。
# Platform: linux かつ /workspace が存在 → devコンテナと判定
if [[ "$(uname)" == "Linux" && -d "/workspace" ]]; then
LOG_DIR="/workspace/logs"
else
LOG_DIR="C:/Users/numbe/path/to/logs"
fi
uname がLinuxを返し、かつ /workspace ディレクトリが存在する場合をdevコンテナと判定する。この2条件の組み合わせで、通常のLinux環境とdevcontainerを区別できた。
差分モードの実装
ログファイルは即録くんが逐次追記していく。3分ごとの実行で毎回ファイル全体を処理すると、同じ内容を繰り返し要約することになる。
そこで差分モードを実装した。処理済みの行数を記録しておき、次回実行時には新規追加分のみを読み取る。
# 前回処理済みの行数を読み込む
LAST_LINE=$(cat "$STATE_FILE" 2>/dev/null || echo "0")
# 新規追加分のみ取得
NEW_LINES=$(tail -n +"$((LAST_LINE + 1))" "$LOG_FILE")
# 今回の総行数を記録
wc -l < "$LOG_FILE" > "$STATE_FILE"
新規分だけをLLMに渡し、前回までの要約と統合する。会議が長くなっても処理量が一定に保たれ、3分のインターバル内に収まる。
minutes.json のリアルタイム更新
Nuxtアプリは app/public/minutes.json を3秒ごとにポーリングして画面を更新する。summarize-meetingスキルの出力をこのJSONファイルに書き込めば、ブラウザにリアルタイムで要約が表示される。
JSONの構造は以下の通り:
{
"status": "recording",
"lastUpdated": "2026-03-22T15:30:00+09:00",
"summary": "要約テキスト...",
"topics": ["トピック1", "トピック2"],
"actionItems": ["アクション1"]
}
ステータスは recording(会議中)と completed(終了)の2値。会議中は3分ごとに要約が更新され、終了後にステータスを completed に切り替える。
worktree問題: ファイルを更新しても画面に反映されない
差分モードも動き、JSONも生成される。ところがブラウザをリロードしても画面が変わらない。JSONのタイムスタンプは更新されているのに、Nuxtアプリ側の表示が古いまま止まっている。
原因
Claude Codeはgit worktree上で作業していた。worktree内の app/public/minutes.json を更新しても、Nuxtの開発サーバーが参照しているのは元リポジトリ側の app/public/minutes.json だった。worktreeと元リポジトリは別のディレクトリツリーを持つため、片方を書き換えてももう片方には反映されない。
修正: 元リポジトリに直接書き込む
worktree内のパスではなく、元リポジトリの app/public/minutes.json の絶対パスを指定して書き込むように修正した。
# NG: worktree内のパス(Nuxtに反映されない)
MINUTES_FILE="./app/public/minutes.json"
# OK: 元リポジトリの絶対パス
MINUTES_FILE="/workspace/original-repo/app/public/minutes.json"
パスを書き換えて再実行した直後、ブラウザの画面に要約テキストが流れ込んできた。worktreeは独立したワーキングディレクトリを持ち、元リポジトリとファイルシステムを共有しない。知っていたはずの性質だが、実際に「書き込んだのに映らない」という症状に出くわすまで結びつかなかった。
45分間のテスト実行
保険営業のロールプレイ(約45分)を題材にテストを回した。即録くんがリアルタイムで文字起こしを行い、3分おきにClaude Codeが新しい発話を拾って要約を更新する。
テスト中に確認できた動作:
- 3分ごとに要約が自動更新される
- トピックリストが会議の進行に合わせて増えていく
- アクションアイテムが抽出される
- 差分モードにより、処理時間が安定している(毎回3分以内に完了)
会議終了後、ステータスを completed に更新し、最終版の要約をmarkdownファイルとして保存した。
振り返り
パス問題は「動かして確認」が速い
devcontainerからWindowsのファイルにアクセスできるかどうか、ドキュメントを読み込むより ls を1回叩くほうが速かった。マウント構成を頭の中で組み立てるよりも、実際にファイルが並ぶのを目で見たほうが確実に前に進む。
worktreeの罠は体感しないと分からない
git worktreeが別のディレクトリツリーだということは知識としては持っていた。だが、「Nuxtの開発サーバーが参照しているのはどちらのディレクトリか」という問いが浮かぶまでに時間がかかった。ファイルを書き込んでいるのに画面が変わらない、という症状からworktreeのパス問題に辿り着くまで、ログの出力先やポーリング間隔など別の箇所を疑って回り道をした。
CronCreate + スキルの組み合わせ
/loop 3m /summarize-meeting という1行を打つだけで定期実行が回り始める。ちょっとしたバッチ処理をその場で試せる。スキル側にロジックを閉じ込めておけば、インターバルを変えたいときもスキルの中身だけ触ればいい。