• #claude-code
  • #リアルタイム要約
  • #devcontainer
  • #worktree
  • #CronCreate
開発misc-devメモ

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行を打つだけで定期実行が回り始める。ちょっとしたバッチ処理をその場で試せる。スキル側にロジックを閉じ込めておけば、インターバルを変えたいときもスキルの中身だけ触ればいい。