45395 - シコウサクゴ -

本番・Pre・開発のターミナルを色で区別する:ヒューマンエラー防止の小技集

2026-04-03
AI駆動開発
AI駆動開発
macOS
iTerm2
ヒューマンエラー防止
DevOps
Last updated:2026-04-05
15 Minutes
2839 Words

「本番環境で開発用のコマンドを実行してしまった」——この種の事故は、環境の取り違えから発生します。本番・Pre・開発の3ワークスペースを1人で運用していると、iTerm2のタブを間違えるだけで本番に影響を与えかねません。

きっかけは単純でした。「iTerm2の本番、Pre、開発のワークスペースそれぞれを開いている時に色を変えたい」——この要望から始まった小技が、7つの多層防御として定着しました。本記事では、環境の取り違えを防ぐ7つの小技と、それぞれの実装方法を記録します。


なぜ「色」なのか

3つのワークスペースを同時に開いていると、以下のリスクがあります。

1
タブ1: 本番環境 ← git push origin main
2
タブ2: Pre環境 ← テスト実行中
3
タブ3: 開発環境 ← 機能実装中
4
5
間違えてタブ1で開発用のデストラクティブなコマンドを実行
6
→ 本番データが破損する可能性

人間は文字列(/Users/htada/data-processing vs /Users/htada/data-processing-pre)を読み分けるより、色を認知する方がはるかに速いです。認知心理学で「プレアテンティブ処理(前注意的処理)」と呼ばれる現象で、色は意識的な注意を向ける前に脳が処理します。


小技1: iTerm2ワークスペースの色分け

iTerm2のプロファイル機能を使い、ディレクトリに応じて背景色を自動切り替えします。

iTerm2プロファイルの設定

  1. iTerm2の Preferences > Profiles で3つのプロファイルを作成
  2. それぞれの背景色を設定
プロファイル名背景色用途
Production暗い赤(#1a0000)本番環境
PreProduction暗い黄(#1a1a00)Pre環境
Development暗い緑(#001a00)開発環境

自動切り替えスクリプト

Terminal window
1
# ~/.zshrc に追加
2
function set_iterm_color() {
3
case "$PROJECT_ROOT" in
4
*/data-processing)
5
# 本番: 赤背景
6
echo -e "\033]1337;SetProfile=Production\a"
7
;;
8
*/data-processing-pre)
9
# Pre: 黄背景
10
echo -e "\033]1337;SetProfile=PreProduction\a"
11
;;
12
*/work/data-processing)
13
# 開発: 緑背景(デフォルト)
14
echo -e "\033]1337;SetProfile=Development\a"
15
;;
7 collapsed lines
16
esac
17
}
18
19
# ディレクトリ移動時に自動実行
20
chpwd() {
21
set_iterm_color
22
}

iTerm2のSetProfileエスケープシーケンスにより、cdでディレクトリを移動するたびに背景色が自動的に切り替わります。赤い背景が見えたら「ここは本番だ」と瞬時に認識できます。


小技2: シェルプロンプトの環境表示

背景色に加えて、プロンプト自体にも環境名を表示します。二重の視覚的フィードバックです。

Terminal window
1
# ~/.zshrc に追加
2
function get_env_label() {
3
case "$PWD" in
4
*/htada/data-processing-pre*) echo "[PRE]" ;;
5
*/htada/data-processing*) echo "[PROD]" ;;
6
*/work/data-processing*) echo "[DEV]" ;;
7
esac
8
}
9
10
# プロンプトに環境名を表示
11
PS1='$(get_env_label) %~ $ '

実際のターミナル表示:

1
[PROD] ~/data-processing $ ← 赤背景 + [PROD]表示
2
[PRE] ~/data-processing-pre $ ← 黄背景 + [PRE]表示
3
[DEV] ~/work/data-processing $ ← 緑背景 + [DEV]表示

注意: case文のパターン順序が重要です。*/htada/data-processing*を先に書くとdata-processing-preもマッチしてしまいます。data-processing-preを先に評価する必要があります。


小技3: 緊急停止ファイル

本番システムを即座に停止させる最もシンプルな方法です。特定のファイルが存在すれば、全プロセスが起動時にチェックして停止します。

1
from pathlib import Path
2
import logging
3
4
logger = logging.getLogger(__name__)
5
6
EMERGENCY_STOP_FILE = Path("/tmp/data_processing_emergency_stop")
7
8
def check_emergency_stop() -> bool:
9
"""緊急停止ファイルの存在をチェック"""
10
if EMERGENCY_STOP_FILE.exists():
11
logger.warning(
12
"Emergency stop file detected: %s. Halting all operations.",
13
EMERGENCY_STOP_FILE,
14
)
15
return True
7 collapsed lines
16
return False
17
18
# 各エントリポイントの冒頭で呼ぶ
19
def main() -> None:
20
if check_emergency_stop():
21
return
22
# ... 通常処理

操作は極めてシンプルです。

Terminal window
1
# 緊急停止(全システム即時停止)
2
touch /tmp/data_processing_emergency_stop
3
4
# 復旧(全システム再開)
5
rm /tmp/data_processing_emergency_stop

なぜデータベースのフラグやAPI呼び出しではなくファイルなのか。理由は2つあります。

  1. 依存なし: DB障害中でもファイル作成は可能
  2. 速度: ファイルの存在チェックはナノ秒オーダー

実際にこの仕組みに助けられたケースがあります。負荷の急上昇時にtouchコマンド一発で全データ処理エンジンを停止できました。DBに接続してフラグを変更するより、パニック時の操作としてはるかに確実です。


小技4: Git pre-pushフックによるブランチ保護

mainブランチへの直接pushを防止します。個人開発でもPR経由のマージを強制します。

.git/hooks/pre-push
1
#!/bin/bash
2
branch=$(git rev-parse --abbrev-ref HEAD)
3
if [ "$branch" = "main" ]; then
4
echo "Direct push to main is not allowed."
5
echo "Use: git push origin feature/xxx && create PR"
6
exit 1
7
fi
8
9
# Pre環境のブランチ命名規則チェック
10
remote="$1"
11
if [[ "$remote" == *"pre"* ]] && [[ "$branch" != pre/* ]]; then
12
echo "Pre remote requires pre/* branch naming."
13
echo "Current branch: $branch"
2 collapsed lines
14
exit 1
15
fi

このフックにより、以下の操作ミスを防止します。

Terminal window
1
# 防止される操作
2
$ git push origin main
3
Direct push to main is not allowed.
4
Use: git push origin feature/xxx && create PR
5
6
# 許可される操作
7
$ git push origin feature/new-35
8
# → 成功。その後PRを作成

小技5: launchdジョブの命名規則

90個のlaunchdジョブを運用する上で、命名規則によるジョブの識別は不可欠です。

1
本番環境:
2
com.dataprocessing.collect.daily_feed_a
3
com.dataprocessing.domestic.phase_alpha.entry
4
com.dataprocessing.overseas.scoring.daily
5
6
Pre環境("pre" を含む):
7
com.dataprocessing.pre.collect.daily_feed_a
8
com.dataprocessing.pre.domestic.phase_alpha.entry

命名規則: com.dataprocessing.{pre.}{engine}.{type}.{target}

この規則により、環境別のジョブ一覧が即座に取得できます。

Terminal window
1
# Pre環境のジョブのみ表示
2
launchctl list | grep "dataprocessing\.pre"
3
4
# データ収集本番のジョブのみ表示
5
launchctl list | grep "dataprocessing\.collect"
6
7
# 全ジョブの環境別カウント
8
echo "本番: $(launchctl list | grep -c 'dataprocessing\.' | grep -v 'pre')"
9
echo "Pre: $(launchctl list | grep -c 'dataprocessing\.pre')"

「preが名前に含まれているかどうか」で本番/Pre環境を即座に判別できます。曖昧な命名(job1, test_jobなど)は絶対に避けます。


小技6: dry-runモードフラグ

本番環境でも安全にテスト実行できるdry-runモードを全エントリポイントに実装します。

1
import sys
2
import logging
3
4
logger = logging.getLogger(__name__)
5
6
def main() -> None:
7
dry_run = "--dry-run" in sys.argv
8
if dry_run:
9
logger.info("[DRY-RUN] No actual operations will be executed")
10
11
# データ取得(dry-runでも実行)
12
signals = generate_signals()
13
logger.info("Generated %d signals", len(signals))
14
15
for signal in signals:
11 collapsed lines
16
task = create_task(signal)
17
18
if not dry_run:
19
result = execute_task(task)
20
logger.info("Executed: %s -> %s", task, result)
21
else:
22
logger.info("[DRY-RUN] Would execute: %s", task)
23
24
# 集計(dry-runでも実行)
25
summary = calculate_summary(signals)
26
logger.info("Summary: %s", summary)

dry-runモードの設計原則は3つあります。

  1. データ取得と分析は実行する: シグナル生成・集計など読み取り系の処理は通常通り実行
  2. 書き込み系のみスキップ: タスク実行・ファイル書き込み・DB更新をスキップ
  3. ログには[DRY-RUN]プレフィックス: 後からログを見て「これはdry-runだった」と判別可能
Terminal window
1
# 本番で安全にテスト
2
python main.py --dry-run
3
4
# 本番実行
5
python main.py

Pre環境のlaunchd plistには、デフォルトで--dry-runフラグを付与しています。

1
<key>ProgramArguments</key>
2
<array>
3
<string>/usr/bin/python3</string>
4
<string>main.py</string>
5
<string>--dry-run</string> <!-- Pre環境はデフォルトdry-run -->
6
</array>

小技7: デイリーヘルスチェック通知(ハートビートパターン)

「通知が来ない = 異常」というハートビートパターンです。毎朝決まった時間にSlack通知を送信し、通知が来なければジョブ自体が停止していると判断します。

daily_health_check.sh
1
#!/bin/bash
2
# launchdで毎朝06:30に実行
3
4
PROJECT_ROOT="${PROJECT_ROOT:-/Users/htada/data-processing}"
5
source "$PROJECT_ROOT/.env"
6
7
# 各ジョブのログ鮮度チェック
8
stale_jobs=""
9
for log_dir in /tmp/dataprocessing_*/; do
10
job_name=$(basename "$log_dir")
11
stdout_log="$log_dir/stdout.log"
12
13
if [ -f "$stdout_log" ]; then
14
last_modified=$(stat -f %m "$stdout_log")
21 collapsed lines
15
now=$(date +%s)
16
age_hours=$(( (now - last_modified) / 3600 ))
17
18
if [ "$age_hours" -gt 24 ]; then
19
stale_jobs="$stale_jobs\n- $job_name (${age_hours}h ago)"
20
fi
21
else
22
stale_jobs="$stale_jobs\n- $job_name (log not found)"
23
fi
24
done
25
26
# Slack通知
27
if [ -n "$stale_jobs" ]; then
28
message="Daily Health Check: WARNING\nStale jobs:$stale_jobs"
29
else
30
message="Daily Health Check: OK ($(date '+%Y-%m-%d %H:%M'))"
31
fi
32
33
curl -s -X POST "$SLACK_WEBHOOK_URL" \
34
-H "Content-Type: application/json" \
35
-d "{\"text\": \"$message\"}"

このヘルスチェックが16日間のサイレント障害を防ぐ仕組みです。ログの最終更新が24時間以上前のジョブがあれば、「そのジョブは動いていない可能性がある」と警告します。

重要なのは「エラー通知」ではなく「正常通知」だという点です。

1
エラー通知方式: エラーが出たら通知する
2
→ エラーが出ない障害(サイレント障害)を検出できない
3
4
ハートビート方式: 毎朝必ず通知する
5
→ 通知が来ない = ジョブ自体が停止 = 異常

7つの小技の多層防御

個々の小技は単純です。しかし組み合わせると、異なるレイヤーでの防御が形成されます。

1
Layer 1: 視覚的防御
2
├── 小技1: iTerm2背景色 ← 環境を色で即座に認識
3
└── 小技2: プロンプト環境表示 ← 文字でも確認
4
5
Layer 2: 操作的防御
6
├── 小技4: pre-pushフック ← 本番への直接pushを遮断
7
└── 小技6: dry-runモード ← 本番でも安全にテスト
8
9
Layer 3: 識別的防御
10
└── 小技5: launchd命名規則 ← 本番/Preのジョブを即座に判別
11
12
Layer 4: 緊急対応
13
└── 小技3: 緊急停止ファイル ← touchコマンドで全停止
14
15
Layer 5: 検出
1 collapsed line
16
└── 小技7: ハートビート ← サイレント障害を検出

1つのレイヤーが突破されても、次のレイヤーが防御します。例えば、背景色を見落としても(Layer 1突破)、pre-pushフックが本番pushを防止します(Layer 2で防御)。


実際の効果

これらの小技を導入してからの3ヶ月間で、以下の効果がありました。

指標導入前(3ヶ月)導入後(3ヶ月)
環境取り違えインシデント4件0件
本番への直接push2件0件(フックで遮断)
サイレント障害の発見平均8.3日後平均0.5日後
緊急停止の実行時間3分(DBフラグ変更)2秒(touch)

最も効果が大きかったのは小技3(緊急停止ファイル)です。負荷急上昇時に「とにかく全部止める」がtouchコマンド一発でできる安心感は、精神的な余裕にもつながります。


学んだこと

1. 環境の取り違えは「色」で防げる

人間は文字より色の方が早く認知します。赤い背景が目に入った瞬間、「ここは本番だ」と無意識レベルで認識できます。意識的に文字列を読んで環境を判断する方式は、疲労時に失敗します。

2. 小技は1つでは弱いが、7つ組み合わせると多層防御になる

iTerm2の背景色だけでは見落とします。pre-pushフックだけではpush以外の操作は防げません。7つの小技を5つのレイヤーに配置することで、1つが突破されても次が防御する多層防御が成立します。

3. 最も効果的なのは「緊急停止ファイル」

技術的には最も単純ですが、実用面での効果は最大です。touchコマンド一発で全システムを停止できる安心感が、本番運用の精神的負荷を大きく下げました。依存関係ゼロ(DB不要、API不要、ネットワーク不要)で動作する点も重要です。


まとめ

ヒューマンエラー防止で重要なのは以下の3点です。

  1. 視覚的フィードバックを最優先: iTerm2の背景色 + プロンプト表示で、環境を「読む」のではなく「見る」ことで認識します。認知負荷を最小化する設計です
  2. 多層防御の構築: 視覚的防御・操作的防御・識別的防御・緊急対応・検出の5レイヤーで、1つの対策が失敗しても次が防御します
  3. シンプルさが信頼性: 緊急停止ファイル(touch/rm)、pre-pushフック(シェルスクリプト)、ハートビート(curl)——複雑な仕組みは障害時に動きません。最もシンプルな手段が最も信頼できます

個々の小技は5分で実装できます。しかしその積み重ねが、本番運用の安全性を根本的に変えます。

Article title:本番・Pre・開発のターミナルを色で区別する:ヒューマンエラー防止の小技集
Article author:45395
Release time:2026-04-03

記事へのご質問・ご感想をお聞かせください

フィードバックを送る