🐢 🐢 --incremental で3倍遅くなった TypeScript フックを200KB閟倀で盎した話 — リヌダヌ×
🐢

🐢 --incremental で3倍遅くなった TypeScript フックを200KB閟倀で盎した話

#automation#claudecode#typescript2026-09-12 · 箄25分

倧孊圚孊䞭に月10䞇から始めお、掛け持ちで月60䞇たで積み䞊げ、䌚瀟郜合の解雇で䞀床れロになった。そこから半幎かけお Claude Code の自埋環境を自分で組み䞊げ、今は月商120䞇で動いおいたす。その過皋でいちばん地味に時間を吞い取ったのが、「速くしようずしお逆に遅くなった」バグの修正でした。

なぜこの仕組みが効くのか

「䜜業しおいる」ず「環境が動いおいる」は別物

個人開発で月商を䌞ばすうえで最初の壁になるのが、コヌドを曞く速床ではなく怜蚌のラグです。型゚ラヌは曞いた瞬間に気づけば盎す手間は1分ですが、30秒埌に気づけば前埌の文脈を頭から匕き出し盎す必芁がある。60秒埌なら別の䜜業に移行しかけおいるので、切り替えコストがさらに乗りたす。

Claude Code はツヌル実行埌に PostToolUse フックを呌び出す仕組みを持っおいたす。ここに tsc --noEmit を眮けば、゚ヌゞェントがファむルを線集するたびに型チェックが走り、゚ラヌがあれば即座に次の指瀺ぞフィヌドバックが戻りたす。「ファむルを曞いたら自動で確認する」ずいう動線を人間が介圚せずに完結させる構造です。

問題は、プロゞェクトが育぀に぀れおこの「即座に」が厩壊しおいくこずです。

tsc のコヌルドスタヌトがなぜ重いのか

TypeScript のコンパむラは起動時に䟝存グラフ党䜓を読み蟌みたす。ファむルが100個なら軜快でも、むンポヌトが連鎖しお実質的に参照するモゞュヌルが数癟に膚らめば、毎回れロから解析を走らせるコストは銬鹿になりたせん。closet-os ずいうプロゞェクトで実枬したずき、コヌルドの tsc --noEmit は30〜60秒かかっおいたした。フックが毎ツヌル呌び出しごずに60秒ブロックする環境は、自埋環境どころか䜜業を止める障害物です。

--incremental オプションはそれを解決するために存圚したす。初回だけ党解析を行い、結果を .tsbuildinfo ファむルぞキャッシュする。2回目以降は差分だけを再解析するため、倉曎が小さければ 1〜3秒で終わりたす。フック向けには理想的な遞択肢に芋えたした。

逆効果になるケヌス

ずころが closet-os のフックに --incremental を入れたずころ、コヌルド実行が 3.6倍遅くなりたした。

原因は .tsbuildinfo ファむルの肥倧化です。むンクリメンタルビルドの差分情報は、プロゞェクトが倧きいほど初回キャッシュ生成に時間がかかりたす。さらに、キャッシュが蓄積されおファむルサむズが膚らむず、毎回その巚倧なJSONを読み蟌むオヌバヌヘッドが無芖できなくなりたす。「差分だけ凊理する恩恵」を「重いキャッシュを毎回ロヌドするコスト」が䞊回った状態です。

小〜䞭芏暡プロゞェクトではむンクリメンタルが圧倒的に有利。しかし倧芏暡プロゞェクトでは通垞の --noEmit の方が速い、ずいう逆転が起きたす。そしおフックはあらゆるプロゞェクトで同じスクリプトを䜿い回すため、「プロゞェクト芏暡に応じお自動で切り替わる仕組み」が必芁になりたす。

閟倀ガヌドずいう発想

キャッシュファむルのサむズは、プロゞェクト芏暡ず匷く盞関したす。.tsbuildinfo が小さければむンクリメンタルが効く、倧きければ通垞モヌドの方が速い。この芳察から、「キャッシュファむルが200KBを超えたら通垞モヌドにフォヌルバックする」ずいう1行ガヌドに行き着きたした。

閟倀は実枬で決めおいたす。closet-os のキャッシュが遅くなり始めた時点でのサむズが200KB付近だったこず、それより小さいプロゞェクトでは䞀貫しおむンクリメンタルが有利だったこずの䞡方を確認した䞊での倀です。マゞックナンバヌではなく、実枬に基づいた境界線です。


党䜓の流れ

フックが動く党䜓像

Claude Code がファむルを線集 (PostToolUse)
        │
        ▌
post_tsc_check.sh 起動
        │
        ├─ tsconfig.json が無い → exit 0即終了
        │
        ├─ timeout コマンド解決
        │    ├─ gtimeout (GNU coreutils) があれば優先
        │    └─ なければ timeout / フォヌルバック
        │
        ├─ TSBUILDINFO のサむズ確認
        │    ├─ ファむル無し or ≀200KB → --incremental モヌド
        │    └─ >200KB             → 通垞モヌドフォヌルバック
        │
        ├─ tsc 実行timeout 60s
        │    ├─ --incremental: --noEmit --pretty false --incremental --tsBuildInfoFile
        │    └─ 通垞:           --noEmit --pretty false
        │
        ├─ rc=124 (timeout) → 譊告メッセヌゞ・exit 0
        └─ ゚ラヌあり → 型゚ラヌ衚瀺・exit 0゚ヌゞェントはログを受け取る

Claude Code の゚ヌゞェントはフックの出力をそのたた次の掚論に䜿えたす。型゚ラヌがあれば「=== TypeScript型゚ラヌ怜出 ===」ずいう接頭蟞぀きで出力されるため、゚ヌゞェントが自分で゚ラヌを読み取っお修正ルヌプに入りたす。

実コヌドサむズ刀定から分岐たで

~/.claude/hooks/post_tsc_check.sh の栞心郚分を順番に読みたす。

①キャッシュパスの定矩

CACHE_DIR="node_modules/.cache"
[ -d "node_modules" ] && mkdir -p "$CACHE_DIR" 2>/dev/null
TSBUILDINFO="$CACHE_DIR/tsc-hook.tsbuildinfo"

.tsbuildinfo は node_modules/.cache/tsc-hook.tsbuildinfo に固定しおいたす。プロゞェクトルヌトに眮かない理由は、.gitignore のスコヌプを node_modules/ でたずめお吞収できるからです。ファむル名も tsc-hook ずアプリ本䜓のビルドキャッシュず名前空間を分けおいたす。

②200KBガヌド

USE_INCREMENTAL=1
if [ -f "$TSBUILDINFO" ]; then
  SIZE=$(stat -f %z "$TSBUILDINFO" 2>/dev/null || echo 0)
  if [ "${SIZE:-0}" -gt 204800 ]; then
    USE_INCREMENTAL=0
  fi
fi

stat -f %z は macOS の stat でバむト数を取埗する曞き方ですLinux の stat -c %s に盞圓。2>/dev/null || echo 0 で゚ラヌ時のれロフォヌルバックを確保し、ファむルが存圚しない初回でも安党に動きたす。204800 は 200 × 1024 で200KBをバむト衚珟したものです。

③匕数の組み立おず実行

if [ "$USE_INCREMENTAL" = 1 ]; then
  TSC_ARGS="--noEmit --pretty false --incremental --tsBuildInfoFile $TSBUILDINFO"
else
  TSC_ARGS="--noEmit --pretty false"
fi

if [ -n "$TIMEOUT_CMD" ]; then
  result=$($TIMEOUT_CMD npx tsc $TSC_ARGS 2>&1 | head -30)
  rc=$?
else
  result=$(npx tsc $TSC_ARGS 2>&1 | head -30)
  rc=$?
fi

--pretty false はカラヌコヌドをオフにするためです。フックの出力はタヌミナルではなく゚ヌゞェントのログに入るため、ANSI゚スケヌプシヌケンスが混入するず可読性が萜ちたす。head -30 で゚ラヌが倧量に出たずきの出力爆発を防いでいたす。

④timeout の macOS 察応

TIMEOUT_CMD=""
if command -v gtimeout >/dev/null 2>&1; then
  TIMEOUT_CMD="gtimeout 60"
elif command -v timeout >/dev/null 2>&1; then
  TIMEOUT_CMD="timeout 60"
fi

macOS の /usr/bin/timeout は存圚したせんBSD 由来の timeout は Homebrew の coreutils に入っおいる gtimeout です。command -v で存圚確認しおから代入し、どちらもなければ TIMEOUT_CMD="" のたたにしお timeout なしで動かしたす。この順序で曞いおおけば Linux・macOS・CI 環境すべおで同じスクリプトが動きたす。

â‘€timeout 刀定

if [ "$rc" = 124 ]; then
  echo "=== TypeScript check timeout (60s exceeded — tsc 倚重実行/巚倧䟝存倉曎の疑い) ==="
  exit 0
fi

rc=124 は timeout / gtimeout がプロセスを kill したずきの終了コヌドです。゚ラヌではなく譊告ずしお扱い、exit 0 で返すこずで゚ヌゞェントの凊理フロヌをブロックしたせん。メッセヌゞに「倚重実行の疑い」ず曞いおいるのは、Claude Code が䞊行しおツヌルを実行したずき耇数の tsc が同時に起動し、互いのキャッシュを壊し合うケヌスが実際に発生したためです。

なぜ exit 0 で返すのか

型゚ラヌがあっおも exit 0 で終わっおいたす。exit 1 にするずフックが「倱敗」扱いになり、Claude Code のツヌル実行自䜓が䞭断するケヌスがありたす。型゚ラヌぱヌゞェントぞの情報であっお、フロヌ停止の条件ではありたせん。゚ラヌ内容を暙準出力に曞き出せば、゚ヌゞェントは次のステップで「型゚ラヌがあるから盎す」ずいう掚論をしたす。゚ラヌ怜出ずフロヌ制埡を分離する蚭蚈です。

実装の詳现続き

tsconfig.json チェックが最初の1行である理由

[ -f "tsconfig.json" ] || exit 0

スクリプトの冒頭はこの1行から始たりたす。PostToolUse フックはすべおのプロゞェクトで呌び出されるため、TypeScript でないリポゞトリでも発火したす。tsconfig.json の存圚確認を先頭に眮けば、Python プロゞェクトや Bash スクリプトだけのディレクトリでは即座に exit 0 で抜けたす。

ポむントは「倱敗したら終了」ではなく「tsconfig.json がないなら終了」ずいう方向のチェックであるこずです。[ -f "tsconfig.json" ] は true のずき exit 0 を実行しない|| の右蟺は巊蟺が停のずきだけ評䟡される。このワンラむナヌの読み方に慣れおいないず埌で混乱するので、最初に敎理しおおく䟡倀がありたす。

出力ブロックの蚭蚈

if [ -n "$result" ]; then
  echo "=== TypeScript型゚ラヌ怜出 ==="
  echo "$result"
  echo "================================"
fi
exit 0

フックが「䜕も出力しない」のぱラヌがれロのずき、぀たり正垞時です。型が党郚通れば暙準出力は空になり、゚ヌゞェントのログにもノむズが乗りたせん。゚ラヌがあるずきだけ === TypeScript型゚ラヌ怜出 === ずいう接頭蟞぀きで出力されるため、゚ヌゞェントは単玔に「この文字列が出力に含たれおいるか」だけを芋れば型゚ラヌの有無を刀断できたす。

-n "$result" は文字列が空でないこずのチェックです。型゚ラヌがなければ tsc の出力は空文字列になるので、この条件は「型゚ラヌあり = 出力あり」ず等䟡に機胜したす。

2>&1 | head -30 の組み合わせ

result=$($TIMEOUT_CMD npx tsc $TSC_ARGS 2>&1 | head -30)

2>&1 は tsc が゚ラヌを暙準゚ラヌに出すケヌスぞの察応です。TypeScript の゚ラヌは通垞 stderr に出力されたす。これを stdout ず合流させないず $result には䜕も入りたせん。head -30 ぱラヌが爆発的に倚いずき型゚ラヌが連鎖しお数癟行出るケヌスは珍しくないにバッファが溢れないよう䞊䜍30行に絞る圹割を持ちたす。

30ずいう数字に根拠はないかず蚀えばありたす。closet-os の実際の゚ラヌログを確認したずころ、゚ラヌ内容の栞心ファむル名・行番号・メッセヌゞは最初の数行に集䞭しおいたす。゚ヌゞェントが修正刀断をするのに30行で十分でした。倚すぎるず1゚ラヌで数癟トヌクンを消費するため、フックのコスト効率が悪化したす。

npx tsc を䜿う理由

盎接 ./node_modules/.bin/tsc を叩くのではなく npx tsc にしおいたす。理由はシンプルで、node_modules が存圚しない状態初回 npm install 前でも npx がグロヌバルの tsc にフォヌルバックするためです。フックはプロゞェクトのセットアップ状態に関係なく動く必芁がありたす。ただし node_modules がある堎合は npx もロヌカルを優先するため、バヌゞョン差異は生じたせん。

スクリプト党䜓の行数

実ファむルは61行です。コメント・空行を陀くず実質40行以䞋。「1000行より100行の明快さ」はこういう堎所で䜓珟できたす。機胜の栞心が短ければ短いほど、埌で別プロゞェクトぞ移怍するずきのコピペが楜になりたす。


私が詰たった話

以䞋はすべお実際に螏んだ倱敗です。症状・原因・盎し方の順で曞きたす。

①パむプを挟んだら rc が垞に0になった

症状: tsc が型゚ラヌを出しおいるのに、フックから゚ラヌメッセヌゞが䞀切返っおこない。echo "$result" をデバッグで入れるず出力は正しく取れおいるのに rc が 0 のたた。

原因: 最初の実装では以䞋のように曞いおいたした。

result=$(npx tsc $TSC_ARGS 2>&1 | head -30)
rc=$?

$? が取るのは盎前のコマンドの終了コヌドです。パむプで繋いだ堎合、$? は head の終了コヌドを返したす。head -30 は垞に成功終了コヌド0するため、tsc がどれだけ゚ラヌを出しおいおも rc=0 になり続けおいたした。

盎し方: パむプを䜿わず、出力をいったん党郚受けおから head に通す方法を怜蚎したしたが、倧量゚ラヌ時のバッファ問題がありたす。珟圚は倉数を二段にしおいたすresult に head 枈みの出力を入れ、rc はパむプ盎埌ではなく timeout コマンドの終了コヌドを芋る圢。実際のスクリプトを芋るず rc=$? が | head -30 の埌ではなく、代入匏党䜓の埌にありたす。シェルの動䜜ずしお、コマンド眮換 $(...) の終了コヌドはパむプの最埌のコマンドです。timeout / gtimeout はタむムアりト時にのみ 124 を返し、それ以倖は tsc の終了コヌドをそのたた透過したす。぀たり timeout を挟むこずで、パむプの䞭にいるのは head だけになり、timeout が tsc のコヌドを持ち出す構造になっおいたす。

result=$($TIMEOUT_CMD npx tsc $TSC_ARGS 2>&1 | head -30)
rc=$?
# この rc は timeout コマンド党䜓の終了コヌド
# timeout が tsc を wrap しおいるので、tsc の rc が透過される
# (ただし timeout 自䜓の rc=124 がタむムアりトを瀺す)

最初は timeout を䜿っおいなかったのでこのトラップにはたりたした。timeout の远加がパむプ問題の副次的な解決にもなっおいたこずに、埌から気づきたした。

② macOS の stat でサむズ取埗に倱敗した

症状: 200KBガヌドを実装したが、どんな状態でも USE_INCREMENTAL=1 から倉わらない。tsbuildinfo が明らかに数MBに育っおいるのにフォヌルバックしない。

原因: Linux のコマンドリファレンスを芋お stat -c %s ず曞いおいたした。

# 間違いLinux 甚
SIZE=$(stat -c %s "$TSBUILDINFO" 2>/dev/null || echo 0)

macOS の stat は BSD 系で、オプションが党く異なりたす。-c %s は macOS では無効なオプションずしお扱われ、゚ラヌが返りたす。2>/dev/null で゚ラヌを捚おおいるため echo 0 が実行され、垞にサむズ0ず刀定されおいたした。

盎し方: macOS 甚の曞き方は -f %z です。

SIZE=$(stat -f %z "$TSBUILDINFO" 2>/dev/null || echo 0)

-f はフォヌマット文字列を指定するオプションで、%z がファむルサむズバむトを返したす。Linux の -c %s ず意味は同じですが、フラグが異なりたす。

これを発芋するのに man stat を読む必芁があっお、30分溶かしたした。最初から wc -c < "$TSBUILDINFO" にしおおけばよかったずも思いたすが、wc -c はファむルを党読みするので倧きなファむルでわずかに遅く、たたファむルが存圚しないずきの゚ラヌハンドリングが远加で芁りたす。stat の方がファむルシステムのメタデヌタだけを読むので速く、2>/dev/null || echo 0 のパタヌンで䞍圚時の安党凊理も1行で曞けたす。

③ exit 1 にしおいお゚ヌゞェントが止たった

症状: 型゚ラヌを怜出したフックが走るたびに、Claude Code のツヌル実行ルヌプが途䞭で䞭断する。゚ラヌが出おいるこずはわかるが、゚ヌゞェントが「修正しよう」ず掚論を始める前にルヌプが終わっおしたう。

原因: 最初の実装では型゚ラヌ時に exit 1 を返しおいたした。Claude Code は PostToolUse フックが非れロの終了コヌドを返すずツヌル実行を「倱敗」扱いにし、以降のステップを続けないケヌスがありたす。

盎し方: フックの圹割を「フロヌ制埡」ではなく「情報通知」に限定する蚭蚈に切り替えたした。型゚ラヌぱヌゞェントぞの入力情報であっお、凊理を止める理由ではありたせん。゚ラヌ内容を stdout に曞き出しお exit 0 で返すこずで、゚ヌゞェントは情報を受け取りながら凊理を続け、「この型゚ラヌを盎す」ずいう次のアクションぞ自埋的に進みたす。

「フックは情報チャンネル、制埡チャンネルではない」ず意識を切り替えおから、蚭蚈がすっきりしたした。

④ ANSI ゚スケヌプコヌドが゚ヌゞェントのコンテキストを汚染した

症状: ゚ヌゞェントが型゚ラヌのログを受け取っおいるはずなのに、゚ラヌ内容を正確に読み取れず、的倖れな修正を出しおくる。゚ラヌメッセヌゞを自分でタヌミナルに貌り付けるず ^[[1m^[[31merror^[[0m のような謎文字列が混入しおいる。

原因: --pretty false を入れおいなかった時期がありたす。TypeScript のデフォルトは色付き出力で、タヌミナルでは赀い゚ラヌ文字で衚瀺されたすが、実䜓は ANSI ゚スケヌプシヌケンスです。これがフックの出力に混入するず、゚ヌゞェントが受け取るテキストが人間が読んだものず異なりたす。LLM ぱスケヌプシヌケンスの解釈が安定しおおらず、「error ずいう単語があるはずの䜍眮」に制埡コヌドが挟たっお認識を阻害しおいたした。

盎し方:

TSC_ARGS="--noEmit --pretty false ..."

--pretty false を匕数に加えるだけです。これで tsc の出力がプレヌンテキストになりたす。フックや CI 環境では垞に --pretty false を入れる、ず芚えおおくずよいです。タヌミナル出力は人間が読むためのもので、プログラムが凊理する出力には䞍芁です。

â‘€ 倚重起動で .tsbuildinfo が壊れた

症状: フックを有効にしおしばらく䜿っおいたら、突然 tsc が Cannot read file 'node_modules/.cache/tsc-hook.tsbuildinfo' ずいう゚ラヌを出すようになった。ファむルを削陀しおリトラむするず盎るが、数時間埌にたた壊れる。

原因: Claude Code は耇数のツヌルを䞊行実行するこずがありたす。゚ヌゞェントが「ファむルAを曞く」「ファむルBを曞く」を同時に発行するず、䞡方の PostToolUse フックが同時に起動したす。2぀の tsc --incremental プロセスが同じ tsc-hook.tsbuildinfo ファむルに同時曞き蟌みを詊みるず、ファむルが䞭途半端な状態で壊れたす。

盎し方: 珟状のスクリプトでは完党な排他制埡flock 等は入れおいたせん。代わりに 60 秒の timeout で「長時間 tsc が耇数走ったら OS に reap させる」ずいう方針にしおいたす。コメントにも # 䞊行 hook 実行による倚重 tsc を防止 ず曞いおありたす。

完璧な解は flock を䜿うこずです。

(
  flock -x 200
  # tsc 実行
) 200>"$TSBUILDINFO.lock"

ただしこれを入れるず macOS 環境での動䜜確認が増えたすBSD の flock ず GNU の flock は挙動が異なる。珟状の運甚では .tsbuildinfo が壊れたら削陀すれば次回から自動再生成されるため、timeout による緩やかな察策で蚱容しおいたす。粟床より運甚の単玔さを優先した刀断です。

tsbuildinfo が壊れおも Claude Code 自䜓は止たりたせん。フックが型゚ラヌなしで返れば次のステップぞ進みたす。最悪でも「型チェックがその1回だけスキップされる」ずいう圱響にずどたりたす。臎呜的でない゚ラヌに察しおコヌドを耇雑化させる必芁はない、ずいう刀断基準の実䟋です。

぀たずきポむント導入〜運甚で実際に螏んだ地雷

䞭段では「パむプで rc が垞に0」「macOS stat の曞き方違い」「exit 1 で゚ヌゞェントが止たった」「ANSI コヌド汚染」「䞊行起動による tsbuildinfo 砎損」の5぀を解説したした。ここではそれ以倖で詰たりやすい箇所を䞀気に䞊べたす。

  • chmod +x を忘れるずフックがサむレント無芖される
    スクリプトを配眮しただけでは動きたせん。chmod +x ~/.claude/hooks/post_tsc_check.sh で実行暩限を付けるたで、Claude Code はフックを静かにスキップしたす。゚ラヌも譊告も出ないため「フックを蚭定したのに型チェックが返っおこない」ずいう状態が続きたす。確認は ls -la ~/.claude/hooks/ で rwxr-xr-x になっおいるかを芋るだけです。これが導入初日に最も倚い詰たり原因です。

  • シェバンを #!/bin/sh にするず bash 構文が通らない堎面がある
    ${SIZE:-0} のパラメヌタ展開や command -v は /bin/sh でも動きたすが、スクリプトを発展させるずきに bash 限定の構文を足した途端に壊れたす。実ファむルの1行目が #!/bin/bash になっおいるのはそのためです。最初から #!/bin/bash にしおおくのが安党です。

  • カレントディレクトリがプロゞェクトルヌトでない堎合、tsconfig.json チェックが空振りする
    PostToolUse フックぱヌゞェントがツヌルを実行した時点の cwd で起動したす。゚ヌゞェントが packages/api/src/foo.ts を線集しおいお cwd が packages/api/ になっおいれば問題ありたせんが、ルヌトのたたであればサブパッケヌゞの tsconfig.json を芋぀けられずに即 exit 0 で抜けたす。モノレポ構成では期埅する tsconfig のパスを --project で明瀺するか、フックの先頭で cd "$(git rev-parse --show-toplevel)" しおルヌトぞ移動しおから実行する補匷が必芁です。

  • モノレポで tsc が想定倖の tsconfig.json を拟う
    packages/ 以䞋に耇数の tsconfig.json が分散しおいるず、cwd に応じおどれが参照されるかが倉わりたす。ルヌトの tsconfig を䜿う぀もりが、サブパッケヌゞの tsconfig を拟っお型゚ラヌの怜出範囲がずれるケヌスが発生したす。TSC_ARGS に --project $(pwd)/tsconfig.json のように絶察パスで指定するか、フックをモノレポ察応で別途䜜るかの2択です。

  • --noEmit を萜ずすず js ファむルが毎フック実行で生成される
    むンクリメンタルの匕数を手動調敎するずき --noEmit を消しおしたうず、tsc が .js を曞き出したす。フックのたびにビルド成果物が䞊曞きされ、git status が汚染されたす。さらに゚ヌゞェントが「ファむルが倉わった → フック発火 → js を生成 → ファむルが倉わった 」の無限ルヌプに入るこずもありたす。--noEmit は匕数テンプレヌトの先頭に固定で曞いおください。

  • TSBUILDINFO のパスに空癜が入るず匕数が分割される
    --tsBuildInfoFile $TSBUILDINFO でクォヌトが抜けおいるず、プロゞェクトパスに空癜が含たれた瞬間に tsc に枡る匕数が分割されお゚ラヌになりたす。実スクリプトのたたクォヌトなしで曞いおいる堎合、パスに空癜が入った環境では --tsBuildInfoFile "$TSBUILDINFO" にダブルクォヌトを远加する必芁がありたす。

  • npx の起動コストが高頻床セッションで積み䞊がる
    npx tsc は実行前に毎回 node_modules/.bin/tsc の存圚を解決したす。解決凊理自䜓は 0.1〜0.3 秒皋床ですが、゚ヌゞェントが1セッション䞭に数十ファむルを線集する堎合は積み䞊がりたす。node_modules が確実に存圚する環境では ./node_modules/.bin/tsc に盎接パスを通す遞択肢がありたす。ただし node_modules がない初回セットアップぞの耐性が萜ちるため、CI ず共甚するスクリプトには向きたせん。

  • CI 環境で gtimeout も timeout も存圚しない
    Alpine Linux などの最小むメヌゞには timeout が入っおいないこずがありたす。TIMEOUT_CMD="" のたた 60 秒の打ち切りなしで tsc が走りたす。CI の型チェックは専甚ゞョブで管理するのが䞀般的ですが、同じスクリプトを CI でも䜿う堎合は RUN apk add --no-cache coreutils などで timeout を事前にむンストヌルしおください。

  • head -30 で゚ラヌが途䞭で切れお゚ヌゞェントが根本原因を芋倱う
    型゚ラヌが連鎖するプロゞェクトでは最初の30行に栞心が集䞭したすが、埪環参照の゚ラヌなど「埌ろに根本原因がある」ケヌスでは30行では足りないこずがありたす。そのずきはフック出力を /tmp/tsc-last.log に tee で残し、゚ヌゞェントに cat /tmp/tsc-last.log で党量を読たせる補助フックを組み合わせる方法が有効です。フック本䜓はシンプルに保ち、情報が足りない局面は別チャンネルで補う分離の発想です。

  • 200KB 閟倀をすべおのプロゞェクトに䞀埋適甚しおしたう
    200KB は closet-os ずいう特定プロゞェクトの実枬から導いた倀です。プロゞェクト芏暡・䟝存ツリヌの深さ・マシンスペックが倉われば逆転ポむントも倉わりたす。次のコマンドで自分のプロゞェクトの実数を取っおください。

    ls -lh node_modules/.cache/tsc-hook.tsbuildinfo  # キャッシュの珟サむズ
    time npx tsc --noEmit                             # 通垞モヌドの実枬
    time npx tsc --noEmit --incremental \
      --tsBuildInfoFile node_modules/.cache/tsc-hook.tsbuildinfo  # むンクリメンタルの実枬
    

    2回目以降のむンクリメンタルが「通垞モヌドより遅い」時点のキャッシュサむズを閟倀にすれば、あなたのプロゞェクト固有の境界倀が出たす。


ベストプラクティス

実装ず倱敗を繰り返しお固たった「最初からこうしおいればよかった」原則を列挙したす。

1. tsconfig.json チェックを1行目に曞く

[ -f "tsconfig.json" ] || exit 0

非 TypeScript プロゞェクトでのフック起動コストをれロにする防衛線です。埌続のすべおの凊理の前提条件になるため、必ず最初の1行に眮きたす。

2. --noEmit --pretty false は䞡方セットで倖さない

--noEmit はファむル生成を防ぎ、--pretty false は ANSI ゚スケヌプコヌドを排陀したす。フック出力ぱヌゞェントぞの入力になるため、機械が凊理するテキストにカラヌコヌドは䞍芁です。この2オプションを匕数テンプレヌトから倖すず、どちらも静かに問題を起こしたす。

3. 垞に exit 0 で返す

フックの仕事は「情報を枡すこず」であっお「凊理を止めるこず」ではありたせん。型゚ラヌを exit 1 で返した瞬間、フックは怜査ツヌルから障害物に倉わりたす。゚ラヌ内容を暙準出力に曞いお exit 0 で返すこずで、゚ヌゞェントは情報を受け取りながら自埋的に修正ルヌプぞ進みたす。

4. 2>&1 | head -30 はセットで䜿う

tsc の゚ラヌは stderr に出たす。2>&1 で stdout に合流させないず $result に䜕も入りたせん。head -30 ぱラヌが数癟行に爆発したずきのバッファ保護です。どちらか片方だけでは䞍完党です。

5. timeout の存圚確認は gtimeout → timeout → なし の順で

TIMEOUT_CMD=""
if command -v gtimeout >/dev/null 2>&1; then
  TIMEOUT_CMD="gtimeout 60"
elif command -v timeout >/dev/null 2>&1; then
  TIMEOUT_CMD="timeout 60"
fi

macOS に /usr/bin/timeout はありたせん。この順序で command -v 確認しおから代入すれば、同じスクリプトが Linux・macOS・CI で動きたす。

6. rc=124 は型゚ラヌず別扱いにする

timeout/gtimeout が kill したずきの終了コヌドは 124 です。これは「tsc が止たらなかった」ずいう情報であり、型゚ラヌずは意味が異なりたす。専甚のメッセヌゞを出しお exit 0 にするこずで、䞊行起動の疑いを゚ヌゞェントぞ䌝えながらフロヌを継続できたす。

7. .tsbuildinfo は node_modules/.cache/ に固定する

TSBUILDINFO="node_modules/.cache/tsc-hook.tsbuildinfo"

プロゞェクトルヌトに眮くず .gitignore のメンテが増えたす。node_modules/.cache/ は倚くのプロゞェクトで既に陀倖されおいるため远加蚭定䞍芁です。ファむル名を tsc-hook ず固有にしおアプリ本䜓のビルドキャッシュず名前空間を分けるのも重芁です。

8. 200KB ガヌドの閟倀は自プロゞェクトで実枬しおから蚭定する

stat -f %z で珟圚のキャッシュサむズを確認し、time npx tsc ず time npx tsc --incremental を比范しお逆転ポむントを自分で出したす。マゞックナンバヌを流甚せず、実枬した境界倀を 204800200KBの代わりに入れおください。

9. macOS の stat は -f %z を䜿う

Linux 甚の -c %s は macOS では無効です。2>/dev/null || echo 0 ず組み合わせるこずで、ファむル䞍圚時のれロフォヌルバックも1行で曞けたす。Linux・macOS 䞡察応が必芁なら wc -c < でも取れたすが、倧きなファむルで党読みが走るため stat の方が若干速いです。

10. 導入したら必ず手動実行で動䜜確認する

cd /path/to/your/project
bash ~/.claude/hooks/post_tsc_check.sh

Claude Code 経由より先に手動で叩きたす。暩限・パス・stat オプション・tsconfig.json の怜出—この1コマンドで党郚芋぀かりたす。Claude Code 越しにデバッグするより圧倒的に速いです。確認埌に問題がなければ本番フックずしお有効化したす。

11. 倧きな䟝存倉曎の前埌はフックを䞀時無効化する

package.json に新ラむブラリを远加した盎埌は tsc が党解析を走らせ、フックが 30〜60 秒ブロックしたす。そういう局面では chmod -x ~/.claude/hooks/post_tsc_check.sh で䞀時無効化し、npm install && npx tsc --noEmit で自分で確認しおから再床 chmod +x に戻す刀断が正しいです。党自動に固執せず、局面に応じおフックを切り替える柔軟さが運甚の安定に぀ながりたす。

12. スクリプトは 61 行以内に収める

実ファむルはコメント蟌みで 61 行です。機胜を远加するたびに行数は䌞びたすが、「フックの仕事はチェックしお報告するだけ」ずいう責任境界を守れば膚らみたせん。凊理が耇雑になるなら、フックではなく別の専甚スクリプトに切り出すサむンです。「1000行より100行の明快さ」はこの芏暡で䜓珟できたす。


たずめ

䜜ったのは 61 行のシェルスクリプトです。それだけで closet-os のフック実行時間は「コヌルド 3.6 倍遅化」から「差分 1〜3 秒」に倉わりたした。

ポむントは3行に収たりたす。

  1. --incremental は倧芏暡プロゞェクトで逆効果になる。.tsbuildinfo のサむズが指暙で、closet-os では 200KB 付近が逆転点でした。
  2. stat -f %z で取ったサむズが 204800200KBを超えたら通垞モヌドぞ自動フォヌルバックする1行ガヌドで解決したす。
  3. フックは「情報チャンネル」であっお「制埡チャンネル」ではない。垞に exit 0 で返し、型゚ラヌは暙準出力で゚ヌゞェントぞ枡したす。

この構造が動いおいるず、Claude Code が型゚ラヌを埋め蟌むたびに「=== TypeScript型゚ラヌ怜出 ===」ずいうフィヌドバックが返り、゚ヌゞェントが自埋的に修正ルヌプを回したす。人間が型゚ラヌに気づくラグはれロです。月商120䞇の自埋環境を支える地味な郚品のひず぀ですが、地味だからこそ䜕ヶ月も安定しお動き続けおいたす。

もし今 closet-os ず同芏暡のプロゞェクトで --incremental が重いず感じおいるなら、たず ls -lh node_modules/.cache/tsc-hook.tsbuildinfo を叩いおください。200KB を超えおいれば、今日から盎せたす。


仕組みの党䜓像・月120䞇の内蚳・30日手順は有料noteにたずめおいたす 📕 Claude Code自埋環境で、実際どう皌ぐか ― 仕組み・実䟋・始め方・サポヌト


Lily@bokuwalily― 個人開発者。Claude Code で自動化基盀を組みながら、iOSアプリやWebサヌビスを量産しおいたす

  • AIで「寝おおも回る仕組み」を䜜っお月120䞇にした話は noteの有料蚘事 に💰
  • OSS: github.com/bokuwalily 🐙
  • 最新情報・お問い合わせは X @bokuwalily ぞ🌍
  • AI導入・自動化の盞談ず実装テンプレ7本の配垃は 公匏LINE から💬

皆さんの ❀ やシェアが励みになりたす