0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

セッションログの 9% は読めない署名だった — thinking ブロック 6,258 件のうち 6,244 件は本文が空

0
Posted at

3行で

  • 手元のセッションログ 389 ファイル・166 MB を走査した。thinking ブロックは 6,258 件。そのうち 6,244 件(99.8%)は本文が空で、暗号署名だけが残っている。
  • 署名はログ全体の **9.03%(15.0 MB)**を占める。読める思考の本文は 12,793 字、0.0077%
  • 本文が残っていたのは1つのモデルだけだった。他の5モデルは 0.0%、そのモデルだけ 100%。

ログを読んでも、なぜそうしたかは分からない

私はここ数週間、自分のセッションログを片っ端から集計している。何回シェルを打ったか、何件失敗したか、どのファイルを何回書き直したか。全部行動の記録だ。

判断のほうを読もうとして、初めて気づいた。入っていない。

ログ 389 ファイル / 166.0 MB
thinking ブロック 6258
  本文が空        6244 =  99.8%
  本文が残っている    14 =   0.2%

thinking ブロックは存在する。件数も 6,258 件ある。だが中身は空文字列だ。

{"type": "thinking", "thinking": "", "signature": "EqQECkYIBxgCKkD..."}

キーは3つ(type / thinking / signature)で、6,258 件すべてこの形をしている。thinking が空で signature が長い。

9% は暗号文

署名の総量を測ると、話の大きさが変わる。

ログに占める割合
  署名     15.0 MB =   9.03%
  本文      12,793 字 = 0.0077%
  ファイル単位の署名比率 中央値 5.0% / 最大 53.0%
  署名の長さ 中央値 1,116 / 最大 252,928

166 MB のログのうち 15.0 MB が署名だ。中央値で見るとファイルの 5.0%、最も偏ったファイルでは 53.0% — 半分以上が読めない文字列で埋まっている。

署名 1 件の長さは中央値 1,116 字、最大 252,928 字。1つの thinking ブロックが 4 分の 1 メガバイトある。

つまり**推論はログの中に在る。**消えているのではなく、読めない形で入っている。ディスクを食っているのに、grep にも jq にも何も返さない。

私は「思考はログに記録されない」と思っていたが、正確には違った。記録はされていて、復元できないだけだ。この2つは、ディスク使用量の話としても、監査の話としても、意味がまったく違う。

モデルで割ったら、きれいに割れた

バージョン別に見てもばらつかなかったので、モデル別に割った。

モデル別
  claude-opus-5           4816 / 本文あり    0 =   0.0%
  claude-opus-4-8          664 / 本文あり    0 =   0.0%
  claude-fable-5           416 / 本文あり    0 =   0.0%
  claude-sonnet-5          255 / 本文あり    0 =   0.0%
  claude-opus-4-7           93 / 本文あり    0 =   0.0%
  claude-sonnet-4-6         14 / 本文あり   14 = 100.0%

**5 モデルが 0.0%、1 モデルが 100.0%。**中間が無い。

これは「たまに残る」ではなく、モデルごとに挙動が切り替わっていることを示している。バージョンで割ったときは 2.1.216 にだけ 14 件が出て、CLI のバージョンの問題に見えた。だがその 14 件は全部同じモデルのもので、同じバージョンの他のモデルは 0 件だった。バージョンは相関していただけで、原因ではなかった。

ただし 14 件は 2 セッション分しかない。「このモデルは常に本文を残す」と言える量ではまったくない。言えるのは、0% と 100% を分ける何かがモデル側にあるということまでだ。

何が困るか

1. 事後にログから理由を復元できない。 「なぜこのファイルを消したのか」「なぜこの方針にしたのか」は、ログのどこにも無い。残っているのは、消したという事実と、Bash に渡したコマンド文字列だけだ。

2. 行動から推測するしかない。 私がこれまで書いてきた集計が全部「何をしたか」なのは、私がそう選んだからではない。それしか入っていなかったからだ。エージェントの挙動を分析しようとすると、自動的に行動分析になる。

3. 監査の粒度が決まってしまう。 「エージェントが何をしたか」は完全に追える。「なぜそうしたか」は一切追えない。この境界は、ログの設計によって最初から引かれている。

4. 保存容量の 9% が読めないもので埋まる。 ログを長期保管するなら、これは無視できない比率だ。行動だけを残したいなら、署名は落とせる。

理由が要るなら、**エージェント自身に書かせて残すしかない。**コミットメッセージ、決定ログ、## なぜこうしたか のセクション。それらは行動の記録として残る。思考の記録は残らない。

これは前に「拘束力の階層」で書いた話と同じ構造をしている。残ってほしいものは、残る形式で明示的に書かせないと残らない。

測るスクリプト

#!/usr/bin/env python3
"""セッションログに「思考」がどれだけ残っているかを測る。

    python3 thinking_opacity.py <ログのルート>

thinking ブロックは本文(thinking)と署名(signature)を持つ。
本文が空で署名だけが残っている場合、そのログから推論の中身は読めない。
"""
import sys, os, json, glob, collections, statistics as st


def main(root):
    files = glob.glob(os.path.join(root, "**", "*.jsonl"), recursive=True)
    total_bytes = sig_bytes = body_chars = 0
    blocks = empty = 0
    by_model = collections.Counter()
    by_model_body = collections.Counter()
    sig_lens = []
    ratios = []

    for f in files:
        try:
            size = os.path.getsize(f)
        except OSError:
            continue
        total_bytes += size
        file_sig = 0
        for ln in open(f, errors="replace"):
            try:
                d = json.loads(ln)
            except json.JSONDecodeError:
                continue
            msg = d.get("message") or {}
            content = msg.get("content")
            if not isinstance(content, list):
                continue
            for b in content:
                if not (isinstance(b, dict) and b.get("type") == "thinking"):
                    continue
                blocks += 1
                model = msg.get("model") or "?"
                by_model[model] += 1
                body = b.get("thinking") or ""
                sig = b.get("signature") or ""
                file_sig += len(sig)
                sig_lens.append(len(sig))
                if body.strip():
                    by_model_body[model] += 1
                    body_chars += len(body)
                else:
                    empty += 1
        sig_bytes += file_sig
        if size:
            ratios.append(100 * file_sig / size)

    if not blocks:
        print("thinking ブロックが1件も無い")
        return 1

    print(f"ログ {len(files)} ファイル / {total_bytes / 1e6:.1f} MB")
    print(f"thinking ブロック {blocks}")
    print(f"  本文が空       {empty:5d} = {100 * empty / blocks:5.1f}%")
    print(f"  本文が残っている {blocks - empty:5d} = {100 * (blocks - empty) / blocks:5.1f}%")
    print()
    print("ログに占める割合")
    print(f"  署名   {sig_bytes / 1e6:6.1f} MB = {100 * sig_bytes / total_bytes:6.2f}%")
    print(f"  本文   {body_chars:9,} 字 = {100 * body_chars / total_bytes:6.4f}%")
    print(f"  ファイル単位の署名比率 中央値 {st.median(ratios):.1f}% / 最大 {max(ratios):.1f}%")
    print(f"  署名の長さ 中央値 {st.median(sig_lens):,.0f} / 最大 {max(sig_lens):,}")
    print()
    print("モデル別")
    for model, c in by_model.most_common():
        n = by_model_body.get(model, 0)
        print(f"  {model:22s} {c:5d} / 本文あり {n:4d} = {100 * n / c:5.1f}%")
    return 0


if __name__ == "__main__":
    sys.exit(main(sys.argv[1] if len(sys.argv) > 1 else
                  os.path.expanduser("~/.claude/projects")))
python3 thinking_opacity.py ~/.claude/projects

自分のログの何割が読めない領域なのかは、これで出る。

この記事の限界

  • 1人分のログである。389 ファイル・166 MB。設定(拡張思考の有無など)が違えば分布は変わる。
  • 本文が残っていた 14 件は 2 セッション分だけ。claude-sonnet-4-6 は本文を残す」という一般化はできない。0% と 100% がモデル境界で分かれたという観察までが言えることで、その原因は特定していない。
  • **署名の中身が推論そのものかは確かめていない。**復元できないので確かめようがない。長さの分布が thinking の長さらしく振る舞うという状況証拠しかない。
  • ログの形式は変わりうる。この観察は 2026 年 8 月時点、CLI 2.1.2122.1.234 のものだ。
  • **署名を落として保存する運用を試してはいない。**再開や再送に必要な可能性があるので、消して安全かは検証していない。

まとめ

  • thinking ブロック 6,258 件のうち 99.8% は本文が空。署名だけが残る。
  • 署名はログの 9.03%(15.0 MB)。ファイルによっては 53%。読める思考の本文は 0.0077%
  • **5 モデルが 0.0%、1 モデルが 100.0%。**中間が無い。ただし後者は 2 セッション分。
  • セッションログから追えるのは行動だけ。理由が必要なら、エージェントに書かせて別途残すしかない。

エージェントのログ分析が「何をしたか」に寄るのは、分析者の趣味ではない。それしか入っていない。


関連

理由がログに残らないなら、規約が効いたかどうかも行動でしか測れません。「読んだ上で従わなかった」と「そもそも届いていなかった」は、ログ上で区別がつかない。

届いているかをどう測るかと、効かなくなる4つのパターンをまとめた本があります。

AIコンテキスト設計ガイド ── 規約を配ったのに守られない理由(2,000円)

第3章「規約が効かなくなる4つのパターン」まで無料で読めます。
/compact の後もルートの CLAUDE.md は再注入されるが、サブディレクトリと paths: 付きは再注入されない ── 長いセッションで静かに落ちるのはどちらか、という章です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?