技術記事、チュートリアル、開発ドキュメント、CI/CD パイプラインの運用手順書において、ビルドログやエラー出力のサンプルを掲載する場面は数多くあります。近年、ドキュメント執筆やスニペットの作成に生成 AI を活用するエンジニアが増えていますが、AI にコマンドの出力例を書かせると、文法的に整った「いかにもそれらしい出力」が一瞬で生成されます。

しかし、その出力が 実機のランタイムや実行環境の条件(対話端末か CI か、バージョン差など)と一致しているか まで確かめているケースは決して多くありません。もっともらしさは、正しさの証拠にはなりません。AI は手元の対話端末での記憶やエラー定義ひな形から「人間が期待しそうな出力」を確率的に合成し、実機では決して出力されない形式を平然と提示してしまうことがあります。

CI 実行ログにおける実機検証とは、生成 AI の推測や作文に頼らず、手元の対話端末(TTY)と CI 環境(非TTY)の出力差異を踏まえた同一条件でコマンドを実行し、実際の挙動と出力を決定論的に照合することです。

AI に対するプロンプト指示には常に「守られないかもしれない」リスクが存在します。「CI ログらしく書いて」「集計行を出さないで」といったプロンプトの工夫だけで正確性を保とうとするのは、無限に広がる原因の空間に対処しようとするアプローチであり、漏れを防ぎ切ることはできません。さらに、大規模言語モデル(LLM)の出力トークンは入力トークンの約 5 倍高価です。正確なログを丸暗記・作文させるのは、コスト面でも精度面でも得策ではありません。以前に 前作の記事 で架空のスペックや条文番号の捏造を取り上げましたが、ツールの出力形式においても全く同様の構造的な罠が潜んでいます。

ドキュメントに載せるツール出力は、AI に作文させず、実機で同一条件のコマンドを実行して得た出力から取り込むべきです。そして、人間の認知的限界を前提に、検証自体を自動テストやプログラムに委ねて人間の判断負荷を許容水準まで減らす設計が必要になります。自社の技術連載制作で実際に発生した事例をもとに、この課題の構造と対策を掘り下げていきます。

実務での失敗:自社連載のCIログに紛れ込んだ「存在しない集計行」

エラー定義ひな形からの推測作文が生んだ虚偽

自社で公開している技術連載記事(非公開リポジトリの制作環境、以下連載記事A、2026-10-23)の執筆セッションにおいて、AI(Claude Code)が連載第 9 話のサンプルとして CI エラー出力を執筆しました 1。

記事本文の草案には、次のような描写が含まれていました。

本文のリンクからログを開いて、いちばん下までスクロールする。最後の行に、集計が出ていた。Found 30 errors in 9 files.

一見すると、CI のビルドログとして何ら不自然さのない、極めて自然な描写に見えます。ところが、セッションの振り返りにおいて、アシスタント自身が環境の改善候補として次のような事実誤認を報告しました 1。

本文に書かれていた error TS2305: ... や Found 30 errors in 9 files. というエラー出力は、TypeScript 公式の diagnosticMessages.json で確認したエラー定義ひな形をもとに推測して作文したものであり、実機のコマンドを実行して確かめた出力ではありませんでした 1 2。モジュール名を囲む引用符の出方(二重引用符 Module '"hakobi-sdk"')も実機では確かめられておらず、公式のエラー定義メッセージ(TS2305 のひな形や TS6261: Found {0} errors in {1} files.)から推測して組み立てられた作文だったのです 1 2。

執筆段階において、AI が「それらしいエラー定義」を参照したことで安心し、実際のコマンド実行を省略したままもっともらしいログを捏造してしまったのです。

スクラッチパッド検証で判明した tsc の TTY / 非TTY 出力仕様

事実を確認するため、スクラッチパッド(使い捨ての一時検証ディレクトリ、後述の mktemp -d による最小環境)を用意し、実際に typescript@5.9.3 を実行して検証しました 1。

まず、CI 環境に相当する非TTY(--pretty false、--noEmit 3)で実行しました。すると、集計行 Found N errors in M files. は 1行も出力されませんでした 1。さらに、エラー行は error TS2305: ではなく a.ts(1,10): error TS2305: とファイル名・行・列で始まったのです 1。

続いて、対話端末に相当する --pretty true(pretty の既定動作 4)で実行しました。すると、集計行 Found 2 errors in 2 files. は出力されたものの、ログの最後の行ではありませんでした 1。集計行の直後には、ファイルごとのエラー件数内訳表(Errors Files)が続いていたのです 1。

つまり、「CI ログの最後の行に集計が出ていた」という記事描写は、CI の出力(非TTY)としても、仮に CI が対話端末(TTY)と同じ装飾出力を行っていたとしても、実機の仕様と二重に食い違っていました。

この挙動を再現するコードと、実際の出力ログは次のとおりです。

コードブロック1: 非TTY(CI相当)での実機再現検証

TypeScript 5.9.3 を非TTY(--pretty false、CI ビルド相当)で実行し、集計行が出力されないこと、およびエラー行がファイル位置から始まることを確認します。

TMPDIR=$(mktemp -d)
mkdir -p "$TMPDIR/node_modules/hakobi-sdk"
cat << 'EOF' > "$TMPDIR/node_modules/hakobi-sdk/package.json"
{"name":"hakobi-sdk","version":"5.3.0","types":"index.d.ts","main":"index.js"}
EOF
cat << 'EOF' > "$TMPDIR/node_modules/hakobi-sdk/index.d.ts"
export function createLabel(o: {to: string}): string;
EOF
cat << 'EOF' > "$TMPDIR/a.ts"
import { createShipment } from 'hakobi-sdk';
createShipment();
EOF
cat << 'EOF' > "$TMPDIR/b.ts"
import { quoteRates } from 'hakobi-sdk';
quoteRates();
EOF
(cd "$TMPDIR" && npx -y -p typescript@5.9.3 tsc --noEmit --pretty false a.ts b.ts) || true
rm -rf "$TMPDIR"

実行結果ログ:

a.ts(1,10): error TS2305: Module '"hakobi-sdk"' has no exported member 'createShipment'.
b.ts(1,10): error TS2305: Module '"hakobi-sdk"' has no exported member 'quoteRates'.

出力行頭に a.ts(1,10): というファイルパスと座標が付与され、末尾に Found N errors... のような集計行は1行も出力されません。

コードブロック2: TTY(端末・pretty相当)での実機再現検証

TypeScript 5.9.3 を --pretty true(対話端末相当)で実行し、集計行の直後にファイル別エラー件数表が続くことを確認します。

TMPDIR=$(mktemp -d)
mkdir -p "$TMPDIR/node_modules/hakobi-sdk"
cat << 'EOF' > "$TMPDIR/node_modules/hakobi-sdk/package.json"
{"name":"hakobi-sdk","version":"5.3.0","types":"index.d.ts","main":"index.js"}
EOF
cat << 'EOF' > "$TMPDIR/node_modules/hakobi-sdk/index.d.ts"
export function createLabel(o: {to: string}): string;
EOF
cat << 'EOF' > "$TMPDIR/a.ts"
import { createShipment } from 'hakobi-sdk';
createShipment();
EOF
cat << 'EOF' > "$TMPDIR/b.ts"
import { quoteRates } from 'hakobi-sdk';
quoteRates();
EOF
(cd "$TMPDIR" && npx -y -p typescript@5.9.3 tsc --noEmit --pretty true a.ts b.ts | sed 's/\x1b\[[0-9;]*m//g' | tail -n 8) || true
rm -rf "$TMPDIR"

実行結果ログ:

           ~~~~~~~~~~


Found 2 errors in 2 files.

Errors  Files
     1  a.ts:1
     1  b.ts:1

集計行 Found 2 errors in 2 files. は出力されますが、直後に Errors Files 表が続くため、ログの末尾行にはなりません。なお、これは TypeScript 5.9.3 での実測結果であり、他のすべてのバージョンで全く同一の挙動を示すと断定するものではありません。しかし、実機を動かして初めて、直感やひな形と実態の差が浮き彫りになります。

公式ソースコードが語る TTY 判定と集計行制御のからくり

なぜこのような挙動になるのか。TypeScript コンパイラのソースコードを確認すると、Node.js のランタイム TTY 状態に完全に連動していることがわかります。

Node.js において、標準出力が端末に接続されている場合、process.stdout.isTTY は true となり、パイプやファイルリダイレクト(CI パイプライン等)では undefined となります 5。

TypeScript の sys.ts(L1533-L1535)では、この process.stdout.isTTY をそのまま sys.writeOutputIsTTY() として参照しています 6。そして executeCommandLine.ts(L159-L168)において、defaultIsPretty は sys.writeOutputIsTTY() が真であり、かつ環境変数 NO_COLOR が未設定のときのみ真を返します 7。

決定打となるのは executeCommandLine.ts(L888-L892)の集計行生成ロジックです。createReportErrorSummary は shouldBePretty が真のときのみ集計関数 getErrorSummaryText を呼び出すコールバックを返し、偽(非TTY)の場合は undefined を返します 7。これにより、CI 環境などの非TTY環境では、集計処理そのものがスキップされます。

さらに watch.ts(L124-L140, L275-L330)の実装では、集計メッセージを出力した直後に createTabularErrorsDisplay によってファイル別エラー件数表を連結しているため、対話端末でも集計行がログの末尾行になることはありません 8。また、エラー行のフォーマットも program.ts(L666-L674, L779-L820)で分岐しており、非TTYでは単行形式(file(line,col):)、TTYでは色・コンテキスト付き形式(file:line:col -)となります 9。

なぜ「もっともらしさ」に頼ると破綻するのか:プロンプト予防の限界とトレードオフ

人間とAIが無意識に抱く「対話端末バイアス」

なぜ AI は存在しない集計行をもっともらしく作文し、人間もレビュー時にそれに気づけないのでしょうか。

エンジニアが普段手元のターミナルで tsc を叩くとき、出力は既定でカラー装飾され、集計行が表示されます(対話端末、TTY 環境、pretty: true 4)。そのため、人間も AI も「ツールのエラー出力といえば、手元の端末で見ているあの形」という強い認知バイアス(対話端末バイアス)にとらわれています。

AI に「CI ログのサンプルを出力して」と依頼しても、AI は学習データやひな形定義(diagnosticMessages.json 2)から「最もありそうな要素(エラー行と集計行)」を統合して作文してしまいます。出力されたログは文法的に正しく整っているため、人間が目視でレビューしても違和感を覚えず、そのまま通過してしまうのです。

プロンプト指示(原因の空間での予防)の構造的限界

「プロンプトに『CI ログだから集計行は出さないで』『非TTYの形式で出力して』と細かく指示すれば防げるのではないか」という疑問を持つかもしれません。

しかし、予防は「原因の空間」に対して設計し、検収は「結果の空間」に対して設計するものです。AI が犯す誤りのパターンや、見落とす文脈(原因)は無数に存在します。プロンプト指示をいくら積み増しても、すべての例外やエッジケースを網羅することはできません。AI に対するプロンプト指示には常に「守られないかもしれない」リスクが存在します。「もっともらしさ」に頼ったプロンプト指示による予防は、本質的に確率的な振る舞いから抜け出せません。

一方で検収は、「得られた出力が、非TTY 実機環境の出力と一致しているか」という結果の性質だけを見れば閉じます。原因を一つひとつ塞ぐのではなく、結果の空間で客観的に判定する方がはるかに堅牢です。

AIに「正確なコピー」をやらせるアンチパターンとコスト構造

AI にログやコードの「正確なコピー・再現」を丸暗記・作文でやらせるべきではありません。

LLM の価格体系において、出力トークンは入力トークンの約 5 倍高価です。出力トークンという最も高価なリソースを大量に消費してログを丸ごと生成させた結果、確率的な揺らぎによって虚偽の集計行が混入してしまっては、コスト面でも品質面でも二重の損失となります。

ファイルコピーやデータ取得のような決定論的タスクには、LLM を使わせずスクリプトやツールなど決定論的なプログラムを実行して取得させるべきです。

ツール出力をドキュメントに載せるアプローチの比較

ドキュメントに掲載するツール出力の作成手法には、「AI によるプロンプト作文」「プロンプト指示+人間目視」「実機実行による決定論的取得」の 3 つが存在しますが、正確性と運用コストの両立には実機実行による取得が最も適しています。

手法取得・検証方法出力の正確性・再現性トークン消費・コスト人間の確認負荷
AIによるプロンプト作文AI が記憶・ひな形から推測してログを作文❌ 確率的(対話端末バイアスによる集計行捏造のリスク)❌ 高価な出力トークンを大量消費(入力の約 5 倍)△ 見落としやすい(もっともらしさに騙される)
プロンプト指示+人間目視詳細な制約プロンプトを与え、人間が目視レビュー△ 漏れやすい(プロンプト無視や人間の見落としが不可避)❌ 長大なプロンプトと出力トークンを消費❌ 極めて高い(仕様を隅々まで確認する認知的限界)
実機実行による決定論的取得スクラッチパッドで実機コマンドを実行し直接取得⭕ 同一環境条件において確実(実機の挙動そのものを反映)⭕ 最小限(コマンド実行とファイル転記のみ)⭕ 極めて低い(実行ログを機械的に検収可能)

AI の作文やプロンプトの工夫に頼るアプローチは、高価な出力トークンを浪費しながら誤った出力を生成するリスクを排除できません。スクラッチパッド等で実機コマンドを走らせて得た出力をそのまま取り込む手法こそが、トークンを節約しつつ確実な品質を担保します。

実機実行アプローチのトレードオフと適用限界

もっとも、実機実行による取得アプローチにも当然ながらトレードオフや適用限界が存在します。読者の中には「AI にコマンドを実行させるのはセキュリティ上危ういのではないか」「すべてのツール出力を毎回実機で走らせると、執筆の手軽さが失われるのではないか」という懸念を持つ方もいるでしょう。

第一に、環境の分離(サンドボックス)が前提となります。AI エージェントに任意のコマンドを実行させる権限を与えると、意図しないファイル破壊や機密情報の漏洩リスクが生じます。そのため、本番環境や既存のリポジトリ上で直接コマンドを打たせるのではなく、使い捨ての一時ディレクトリ(スクラッチパッド)やコンテナ環境などの隔離された空間に限定し、最小限の再現コードに絞って実行させることが運用の防壁として不可欠です。

第二に、外部認証や長大なビルド時間を要するツールに対する割り切りです。数十分かかる大規模プロジェクトのフルビルドや、外部の有償 API へのアクセスが必要なツール出力を都度実機で再現させるのは現実的ではありません。そうしたケースでは、AI に出力全体を推測させるのではなく、最小限の入力で再現できるモック環境を作るか、あるいは人間が手元で過去に取得した確定ログを静的なスニペットとして渡し、AI にはその転記のみを許可する(作文を禁止する)といった境界の設計が重要になります。

解決策:AIに作文させず、決定論的ツールで取得・検収させる

執筆スキルへの実機実行ルールの導入

事実誤認の発覚後、自社パイプラインでは直ちに 3 つの修正を行いました 1。

  1. 連載記事本文の修正: 「最後の行に集計が出ていた」描写を削除し、実際の非TTYエラー行の並びに修正。
  2. テスト用スニペット(ci-error.txt)の修正: 「TypeScript 5.9.3 を npx で走らせて形を確かめた」注記とともに実機出力へ差し替え。
  3. 管理台帳の修正: 集計行の記述を実機仕様に合わせて修正。

そして再発を防ぐため、エージェントの執筆スキル(SKILL.md)に明確なルールを追加しました 1。

コードブロック3: 執筆スキルに追加した実機実行ルールの抜粋

再発防止策として執筆スキルに明文化された「実機実行と同一条件再現」のルール規定を示します。

引用元: 筆者の検証セッションログ(2026-10-23)における執筆スキル設定ファイル(SKILL.md)

ファイル: SKILL.md

**本文に載せるツールの出力は、実際に走らせて写す**: コンパイラのエラー文・集計行など、物語に出る道具の出力(テストの対象外)も、ひな形や記憶から組み立てず、スクラッチパッドに最小の再現を作って走らせ、その出力から `snippets/` に写す。npm パッケージが要る道具は `npx -y -p <パッケージ>@<版> <コマンド>` でスクラッチパッドの中だけで使い、検証済みコードの依存には足さない(テストの依存ゼロは保つ)。端末かどうかで出力が変わる道具は、物語の場面(CI のログなら端末ではない)に合わせたオプションで走らせる。走らせたコマンドと版を完了報告に書く(code-falconer ep09 で `tsc` の集計行 `Found N errors in M files.` を、CI のログの最後の行として本文に載せた。実際は端末でない出力には出ず、出る場合も後ろにファイルごとの表が続く)

ツール出力を記憶や推測で組み立てることを禁止し、スクラッチパッドでの最小再現・実機コマンド実行・場面に応じたオプション指定を義務付けています。

人間の認知的限界と自動検収の将来展望

成果物の確認(特にコードレビューやログ精査)は、人間の認知的限界を超えています。確認自体も AI や自動テストに担わせ、人間が判断すべき量を許容できる水準まで減らす設計が必要です。

人間が数千行のログやドキュメントをすべて目視で精査することは不可能であり、「もっともらしさ」にだまされるのは人間も同じです。

将来的には、エージェント実行ハーネス自体が、ドキュメント内のコードブロックや実行例を自動検出し、隔離されたコンテナ環境で実際に実行して出力差分を機械的に検収するパイプラインが標準化されていくと考えられます。検証を決定論的なプログラムに委ねることで、人間は「何を出力させるべきか」「読者にどんな価値を届けるか」という本質的な意思決定に集中できるようになります。

よくある質問(FAQ)

Q. tscのCI実行ログで集計行が出ない理由とは何か?

A. tsc は標準出力が対話端末(TTY)に接続されている場合(または --pretty true 指定時)にのみ集計行 Found N errors in M files. を出力する実装になっているためです 7。CI 環境などの非TTY環境では --pretty が既定で無効化され、集計処理そのものがスキップされます。

Q. tscのエラー出力におけるTTY(pretty)と非TTYの違いは?

A. 非TTY環境(CI等)では色装飾や集計行が出力されず、エラー各行が ファイル名(行,列): error TSxxxx: の形式で始まります 9。一方、TTY環境(端末)では色装飾とともに ファイル名:行:列 - error TSxxxx: とハイフン区切りで表示され、複数ファイルエラー時には集計行の直後にファイル別の内訳表(Errors Files)が出力されます 8。

Q. 生成AIに技術記事のCIエラーログを書かせるときの注意点は?

A. 生成 AI にプロンプト指示だけで CI ログを作文させず、必ずスクラッチパッド等の実機環境で同一条件(非TTY)のコマンドを実行させ、その出力を直接取り込むことです。AI はエラー定義ひな形や対話端末の記憶から整った集計行をもっともらしく捏造しやすいため、プロンプトでの予防ではなく実機実行による決定論的検収が不可欠です。

まとめ

  1. ツール出力は AI に作文させず、実機で同一条件のコマンドを実行して得た出力から取り込む: もっともらしさは正しさの証拠になりません。スクラッチパッド等の最小環境で実際にコマンドを走らせることが唯一の品質担保となります。
  2. 手元の端末(TTY)と CI 環境(非TTY)の出力差異を意識する: 普段ターミナルで見ている出力(pretty、カラー装飾、集計行)は CI パイプラインの出力とは異なります。ドキュメントに掲載する場面の環境条件を正確に再現する必要があります。
  3. 高価な出力トークンでログを丸暗記・作文させない: LLM の出力トークンは入力の約 5 倍高価です。データの取得やファイルコピーは決定論的なスクリプトやツールに委ねるべきです。
  4. 予防は原因の空間に、検収は結果の空間に対して設計する: プロンプト指示をいくら積み増しても確率的な漏れは防げません。得られた出力が実機仕様と一致しているかを外部テストで検証する検収設計が不可欠です。
  5. 人間の認知的限界を前提に、検証自体を自動化する: ドキュメントやコードの全量目視チェックは破綻します。検証をプログラムや自動パイプラインに担わせ、人間が判断すべき負荷を許容範囲に抑える設計が重要です。

開発プロセスの標準化やコンテンツ制作パイプラインの品質管理、AI Operations の導入設計について、ぜひ コンタクトフォーム よりお気軽にご相談ください。

Footnotes

  1. 筆者の検証セッションログおよび制作リポジトリの修正記録 2026年10月22日〜10月23日、自社パイプライン実務記録。技術連載記事制作における Claude Code の推測作文発覚、スクラッチパッドでの実機検証ログ(§2、§3)、記事・スニペット・台帳の修正、および執筆スキルへのルール追加の一次記録。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11

  2. TypeScript v5.9.3 定義: diagnosticMessages.json Microsoft、2025年9月、Git リポジトリ(tag: v5.9.3)。L1931-L1934(TS2305: Module '{0}' has no exported member '{1}'.)、L5640-L5643(TS6261: Found {0} errors in {1} files.)。AI が実機未確認のまま推測作文の根拠としたひな形定義の原典。 ↩ ↩2 ↩3

  3. TSConfig Reference: noEmit Microsoft、2026年確認。noEmit オプションの役割(JavaScript 等の出力ファイルを生成せず型チェック等を行う)の根拠。 ↩

  4. TSConfig Reference: pretty Microsoft、2026年確認。pretty オプションの役割(コンパイラメッセージの色・装飾)および既定値が true である仕様の根拠。 ↩ ↩2

  5. Node.js API Docs: writeStream.isTTY OpenJS Foundation、2026年確認。writeStream.isTTY が対話端末で true、パイプやリダイレクトで未定義となるランタイム仕様の根拠。 ↩

  6. TypeScript v5.9.3 ソースコード: sys.ts Microsoft、2025年9月、Git リポジトリ(tag: v5.9.3)。L1533-L1535(Node.js 環境における writeOutputIsTTY メソッドが process.stdout.isTTY を返すブリッジ実装の根拠)。 ↩

  7. TypeScript v5.9.3 ソースコード: executeCommandLine.ts Microsoft、2025年9月、Git リポジトリ(tag: v5.9.3)。L159-L168(defaultIsPretty における sys.writeOutputIsTTY() および NO_COLOR 判定)、L888-L892(createReportErrorSummary における集計行コールバック制御。非TTY では undefined となり集計がスキップされる実装)。 ↩ ↩2 ↩3

  8. TypeScript v5.9.3 ソースコード: watch.ts Microsoft、2025年9月、Git リポジトリ(tag: v5.9.3)。L124-L140(createDiagnosticReporter におけるフォーマッタ分岐)、L275-L298(getErrorSummaryText における集計行生成)、L300-L330(createTabularErrorsDisplay におけるファイル別エラー件数表の出力)。対話端末でも集計行が末尾行にならない実装の根拠。 ↩ ↩2

  9. TypeScript v5.9.3 ソースコード: program.ts Microsoft、2025年9月、Git リポジトリ(tag: v5.9.3)。L666-L674(formatDiagnostic による非TTY 単行カッコ表記 file(line,col): の生成)、L779-L820(formatDiagnosticsWithColorAndContext による TTY 装飾形式 file:line:col - の生成)。 ↩ ↩2