朝、/tmp に残しておいた前夜のログを開いて、しばらく画面を見つめていました。1,800 行あるはずのファイルの大半が、同じ進捗表示の繰り返しでした。「Thinking…」の右にあるスピナーが、1 コマごとに 1 行ずつ、律儀に積み上がっている。
ターミナルで見ていたときは、あの行は 1 行しか存在しなかったはずです。カーソルが戻り、同じ場所が塗り替えられ、私の目には 1 行として映っていた。ログファイルはその塗り替えの過程を、すべて正直に記録していただけでした。
端末に映るものと、パイプの向こうに流れるものは、別物です。頭では分かっていたつもりでしたが、自分のログを実際に数えるまで、その差の大きさを見誤っていました。
描画そのものを切り替える設定がある
Claude Code には screen reader mode というオプトイン設定があります。名前のとおり、スクリーンリーダー利用者のために用意された設定で、有効にすると出力がプレーンテキスト描画に切り替わります。
支援技術を使わない人にとっても、この設定が意味を持つ場面があります。スクリーンリーダーが読み上げに困る画面と、grep や diff が扱いに困るログは、原因が同じだからです。カーソル移動で同じ位置を塗り替える描画、色を付けるためのエスケープシーケンス、幅に合わせた折り返し — これらは目で追う分には親切ですが、機械が後から読むときには全部ノイズになります。
つまり screen reader mode は、アクセシビリティの設定であると同時に、「この出力を後から読み返すのか」という問いへの答えでもあります。私自身は前者の必要があって知った設定ではなく、後者に困って辿り着きました。
三つの有効化経路と、その使い分け
有効化の方法は三つ用意されています。同じ結果になるなら一つでよさそうなものですが、効く範囲が違うので、実際には使い分けが要ります。
経路 書き方 効く範囲 私の使いどころ
CLI フラグ claude --ax-screen-readerその 1 セッションのみ まず試す。戻すのも起動し直すだけ
環境変数 CLAUDE_AX_SCREEN_READER=1そのシェル/そのプロセスの子孫 ログを取る実行だけに前置きで効かせる
設定ファイル "axScreenReader": trueその環境の全セッション 常用が決まってから恒久化する
運用上いちばん出番があるのは、環境変数の経路でした。前置きで書けるので、ログを残したい実行にだけ効かせられます。
# その 1 回だけプレーンテキスト描画にして、生の出力を保存する
CLAUDE_AX_SCREEN_READER = 1 claude -p "src/config 配下の重複した定数をリストアップして" \
> " $HOME /logs/claude-$( date +%Y%m%d-%H%M%S).log" 2>&1
設定ファイルに書く場合は、settings の JSON に 1 行足します。
{
"axScreenReader" : true
}
順番としては、フラグで試す → 環境変数で必要な実行に効かせる → 手元の全セッションで欲しくなったら設定ファイルへ、という流れが素直です。いきなり設定ファイルに書くと、「今どちらの描画で動いているのか」が分からなくなったときに切り分けが増えます。
ログの実寸を測る
体感で決めたくなかったので、自分のログで測りました。測るのは三段階です。生のバイト数、ANSI 制御シーケンスを除いたバイト数、そして復帰(\r)による上書きを畳んだあとのバイト数。
#!/usr/bin/env python3
"""claude-log-weigh.py — Claude Code のログから、装飾と再描画の重さを測る。
usage: python3 claude-log-weigh.py <logfile>
"""
import re
import sys
from pathlib import Path
# CSI シーケンス(色・カーソル移動)と OSC シーケンス(タイトル設定等)
ANSI_RE = re.compile( rb " \x1b\[ [ 0-9;? ] * [ -/ ] * [ @-~ ] | \x1b\] [ ^ \x07\x1b ] * (?: \x07 | \x1b\\ ) " )
def collapse_cr (data: bytes ) -> bytes :
"""行内の \\ r 上書きを、端末と同じく「最後の描画だけ」に畳む。"""
out = []
for line in data.split( b " \n " ):
# \r で区切られた最後の断片が、実際に画面へ残っていたもの
out.append(line.split( b " \r " )[ - 1 ] if b " \r " in line else line)
return b " \n " .join(out)
def dedupe_adjacent (lines: list ) -> list :
"""隣り合う完全一致行を 1 行に畳む(スピナーの残骸対策)。"""
result = []
for line in lines:
if not result or result[ - 1 ] != line:
result.append(line)
return result
def main (path: str ) -> None :
raw = Path(path).read_bytes()
stripped = ANSI_RE .sub( b "" , raw)
collapsed = collapse_cr(stripped)
final = b " \n " .join(dedupe_adjacent(collapsed.split( b " \n " )))
stages = [
( "raw" , raw),
( "ansi-stripped" , stripped),
( "cr-collapsed" , collapsed),
( "dedup-adjacent" , final),
]
base = len (raw)
for name, data in stages:
size = len (data)
lines = data.count( b " \n " ) + 1
print ( f " { name :16 } { size :9,d } bytes { lines :6,d } lines ( { size / base :5.1% } of raw)" )
out = Path(path).with_suffix( ".clean.log" )
out.write_bytes(final)
print ( f " \n wrote: { out } " )
if __name__ == "__main__" :
if len (sys.argv) != 2 :
sys.exit( "usage: python3 claude-log-weigh.py <logfile>" )
main(sys.argv[ 1 ])
collapse_cr を入れているのがこのスクリプトの肝です。ANSI を除くだけでは、冒頭の「同じ行が何十回も」は消えません。あれは装飾ではなく、\r でカーソルを行頭に戻して書き直した履歴だからです。装飾の除去と再描画の畳み込みは、別の作業として分けて数える必要があります。
既定の描画のまま取ったリファクタリング作業のログ(生 1.9 MB / 約 24,000 行)を私の手元で通すと、こうなりました。
段階 サイズ 行数 生に対する比
raw 約 1.9 MB 約 24,000 100%
ansi-stripped 約 0.9 MB 約 24,000 約 48%
cr-collapsed 約 0.6 MB 約 24,000 約 30%
dedup-adjacent 約 0.2 MB 約 2,600 約 11%
同じ作業を CLAUDE_AX_SCREEN_READER=1 を前置きして取り直すと、生のログが 0.2 MB 台に収まりました。最終段まで通した結果とほぼ同じ大きさです。この設定を入れるということは、後処理でやっていた三段階を、記録の時点で済ませておくことに近い。
ここで大事なのは 11% という比率そのものではありません。数字は作業の内容で動きます。効いたのは、「装飾で 5 割、再描画でさらに 2 割弱」という内訳が見えたことでした。ログの肥大をエスケープシーケンスのせいだと思い込んでいた私の見立ては、半分しか当たっていませんでした。行数を本当に減らしていたのは、最後の重複畳み込みです。
落とすものもある
いいことばかりではありません。プレーンテキスト描画にすると、失われるものがあります。私が実際に「戻したい」と感じたのは次の三つでした。
進捗の上書き表示 。実行中のスピナーや進行状況が、上書きではなく行の追加として流れます。画面が下へ下へと動くので、長い処理を眺めているときの落ち着きがなくなります。
差分の色分け 。編集提案の追加行と削除行が、色ではなく記号だけで区別されます。ざっと目視で確認する速度は、体感で目に見えて落ちました。逆に、その画面をコピーして別の場所へ貼るときには、余計な装飾が付いてこないぶん扱いやすくなります。
折り返しの調整 。端末幅に合わせた整形が控えめになるため、狭い画面では読みにくくなる場面があります。私は分割したペインの片側で動かすことが多いので、ここは無視できませんでした。
対話しながら手を動かす作業には既定の描画のほうが向いていて、後から読み返す前提の実行にはプレーンテキスト描画が向いている。素直な線引きですが、設定ファイルで常時オンにする前に、この線引きが自分の作業に合うかを一度確かめておくと安全です。
自動実行の側にだけ固定する
私の場合、答えは「対話は既定のまま、自動実行だけプレーンテキスト」でした。環境変数の経路が効くのはここです。ラッパーを 1 枚挟んで、非対話で走らせる Claude Code は必ずその経路を通るようにしておきます。
#!/usr/bin/env bash
# claude-batch — 非対話実行用のラッパー。ログを素のまま残す。
set -euo pipefail
LOG_DIR = "${ CLAUDE_BATCH_LOG_DIR :- $HOME / logs / claude }"
mkdir -p " $LOG_DIR "
LOG = " $LOG_DIR /$( date +%Y%m%d-%H%M%S)- $$ .log"
# プレーンテキスト描画 + 色の抑止。両方入れておくと取りこぼしが減る
export CLAUDE_AX_SCREEN_READER = 1
export NO_COLOR = 1
{
echo "# started: $( date -Iseconds )"
echo "# argv: $* "
} >> " $LOG "
# 終了コードを握りつぶさないよう、パイプではなくリダイレクトで受ける
set +e
claude " $@ " >> " $LOG " 2>&1
STATUS = $?
set -e
echo "# finished: $( date -Iseconds ) status=${ STATUS }" >> " $LOG "
echo " $LOG "
exit " $STATUS "
set +e で囲っているのは、終了コードを呼び出し元へそのまま返すためです。ここを claude ... | tee の形にすると、パイプの最後のコマンドの結果が返ります。テストがエラーで落ちても呼び出し元には成功が伝わる、という取り違えです。夜間に回している実行では、これが数日気づかれないまま残りました。私はこの回避のためだけに、ラッパーを一枚挟むようにしています。
NO_COLOR も併せて渡しています。screen reader mode があれば十分に思えますが、Claude Code が呼び出す先のコマンド — テストランナーやリンター — の色までは面倒を見てくれません。ログを素にしたいなら、外側から一段広く抑えておくほうが確実です。
複数のサブエージェントが並列に走る実行では、ログの量そのものが跳ね上がります。何をどこまで任せるかという判断については、Dynamic Workflows の規模と effort を、勘ではなく手元の台帳で決める で別途整理しています。記録の形を整えるのは、その台帳を作る前の下ごしらえにあたります。
描画の詰まりとの関係
2026-07 に入って、非常に長いリスト・表・段落・コードブロックを含む応答のストリーミング中に、ターミナルが固まったりキー入力が遅れたりする不具合が修正されました。心当たりのある方は多いと思います。私も、生成された長い表が流れ始めた瞬間に手元が効かなくなる場面を何度か踏みました。
この修正が入った以上、描画の詰まりを避けるために screen reader mode を使う理由はもうありません。念のため書いておくと、この設定は詰まりの回避策ではなく、出力の形式そのものを変えるものです。目的が違うので、代用として使うと期待外れになります。
一方で、詰まりが直ったことで、長い出力をそのまま流す機会は増えました。増えたぶん、ログの重さは効いてきます。順序としては、まず最新版へ上げて詰まりの修正を取り込む。そのうえで、後から読み返す実行だけ描画を素にする。この二つは競合しません。
いま私はこう切り替えている
半月ほど運用してみて、落ち着いた形はこうなりました。
場面 描画 指定の仕方
手を動かしながらの対話 既定 何も指定しない
夜間・非対話の一括実行 プレーン ラッパーで CLAUDE_AX_SCREEN_READER=1
不具合の記録を人に渡す プレーン その場だけ --ax-screen-reader
長い出力をそのまま貼る プレーン 同上
設定ファイルへの恒久化は、結局しませんでした。個人開発では対話と自動実行の比率が日によって振れるので、環境変数で切り替えられる状態のほうが扱いやすかったからです。自動実行専用の環境を分けて持っている方なら、そちらの settings にだけ axScreenReader を書いてしまうほうをお勧めします。この場合は切り替えの必要がないので、恒久化しても迷いが生まれません。
キー操作の設定でいえば、同じ更新で vim モードの挿入モードに 2 キーのシーケンス(jj を Escape に割り当てる、といった)を登録できる vimInsertModeRemaps も入りました。描画とは別軸ですが、手元の端末体験を自分の側に寄せるという点では同じ方向を向いています。触れる設定が増えたら、一度ずつ確かめておく価値はあります。
次の一歩
まず、いちばん最近の非対話実行のログを 1 本、上のスクリプトに通してみてください。raw と dedup-adjacent の比が 5 割を切っていたら、そのログは半分以上が描画の履歴です。そこまで見えれば、設定を切り替えるかどうかは自然と決まります。
私自身、この設定にはアクセシビリティの入口から入ったわけではありませんでした。それでも、支援技術のために用意された「素のまま出す」という選択肢が、そのまま機械可読なログの正解でもあった、というのは腑に落ちる話でした。読みやすさを誰のために整えるのかを分けて考えると、設定の意味が変わって見えてきます。お読みいただきありがとうございました。