この記事が良かったら
「チップをリクエスト」で著者にチップの受け取り設定をお願いできます

--- 「Claude Codeを無人で自律改善させる」前作との続きで、今回は環境の肥大化を自覚する仕掛けの話です。 \ 配下にスキルが積み重なり、launchdジョブが増え、プロジェクトが二桁を超えたとき、「全体が何層あって今何が動いているか」を即答できなくなりました。毎朝 \…
この記事が良かったら
「チップをリクエスト」で著者にチップの受け取り設定をお願いできます
「Claude Codeを無人で自律改善させる」前作との続きで、今回は環境の肥大化を自覚する仕掛けの話です。
`~/.claude/` 配下にスキルが積み重なり、launchdジョブが増え、プロジェクトが二桁を超えたとき、「全体が何層あって今何が動いているか」を即答できなくなりました。毎朝 `ls` を打つのではなく、Obsidianを開けば俯瞰できる状態を目指して、`~/.claude/scripts/env-map.sh` を書きました。
現状のスナップショットはこうです。
スキル・ジョブ・プロジェクトそれぞれの最終更新やgit状態がバラバラで、毎回確認しに行くコストが積み重なります。「今日のautopilotは何をした?」「あのプロジェクトのブランチは?」を1ページで答えられる場所が欲しかった。
図にするのは3層です。
出力先はObsidian Vault(`~/Documents/claude-obsidian/wiki/meta/environment-map.md`)。vault-auto-ingest(4:55)が拾って自動コミットするので、バージョン管理もタダで付いてきます。
スクリプトの設計方針はコメントに書いてあります。
# 何が起きても生成を完走させる(個別の収集失敗は "?" で degrade)。set -e は使わない。
set -uo pipefail`set -e` を使わないのがポイント。MCP接続確認などで部分的に失敗しても `?` を埋めて最後まで出力させます。
launchdから起動するとPATHは `/usr/bin:/bin:/usr/sbin:/sbin` 程度しかない。`node`も`claude`も`jq`も見つかりません。スクリプトの先頭でPATHを明示的に組み立てます。
NVM_BIN="$(ls -d "$HOME"/.nvm/versions/node/*/bin 2>/dev/null | sort -V | tail -1)"
export PATH="$HOME/.local/bin:/opt/homebrew/bin:/opt/homebrew/sbin:${NVM_BIN:+$NVM_BIN:}/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"nvmはnodeバージョンを上げるとパスが変わるので、`sort -V | tail -1` で最新版binを動的に解決しています。固定パスを書くと次のバージョンアップで壊れます。
launchdの最小PATHで詰まるのは「ユーザが入れたCLI全般」です。`~/.local/bin`(uv・claude等)→ Homebrew → nvm の順でフォールバックを積んでおくと、どの環境構成でも当たります。
AUTO_SKILLS="$(ls "$HOME/.claude/skills/auto/" 2>/dev/null | grep -vc README)"
AGENTS="$(find "$HOME/.claude/plugins" "$HOME/.claude/agents" -path '*/agents/*.md' \
-o -path "$HOME/.claude/agents/*.md" 2>/dev/null | wc -l | tr -d ' ')"
PLUGINS="$(jq -r '.enabledPlugins // {} | length' "$SETTINGS" 2>/dev/null || echo '?')"
HOOK_EVENTS="$(jq -r '.hooks // {} | keys | length' "$SETTINGS" 2>/dev/null || echo '?')"
LAUNCHD="$(ls "$HOME/Library/LaunchAgents/"com.shun.*.plist 2>/dev/null | wc -l | tr -d ' ')"MCPだけは注意が必要です。`claude mcp list`はライブ接続を試みるので起動が遅く、失敗することもある。タイムアウトで押さえます。
MCP_OK="?"
if have claude; then
_mcp="$(timeout 12 claude mcp list 2>/dev/null)"
[ -n "$_mcp" ] && MCP_OK="$(printf '%s' "$_mcp" | grep -c 'Connected')"
fi`timeout 12` で12秒経ったら諦め、`?` のままにします。生成は止めません。
リポジトリごとにbranch・最終コミット日・未コミット変更数を取得して、ノードの色に反映させます。
proj_meta() {
local path="$1"
PROJ_EXISTS=0; PROJ_BRANCH="-"; PROJ_LAST="-"; PROJ_DIRTY=0
[ -d "$path" ] || return
PROJ_EXISTS=1
if git -C "$path" rev-parse --git-dir >/dev/null 2>&1; then
PROJ_BRANCH="$(git -C "$path" rev-parse --abbrev-ref HEAD 2>/dev/null || echo '-')"
PROJ_LAST="$(git -C "$path" log -1 --format=%cd --date=format:%Y-%m-%d 2>/dev/null || echo '-')"
PROJ_DIRTY="$(git -C "$path" status --porcelain 2>/dev/null | wc -l | tr -d ' ')"
fi
}未コミット変更があるプロジェクトはオレンジ、ディスクに存在しないプロジェクトは赤で表示します。
echo ' classDef dirty fill:#3a2a00,stroke:#e8a33d,color:#fff;'
echo ' classDef gone fill:#3a1a1a,stroke:#e06666,color:#fff;'朝Obsidianを開いただけで「どのプロジェクトにコミット漏れがあるか」が色で分かります。
ホスト名やチップ名に `()` や `[]` が入るとMermaidのパースが壊れます。全ラベルをサニタイズ関数に通します。
san() { printf '%s' "$1" | tr '"[]()#|<>' ' ' | tr '\n' ' ' | sed 's/ */ /g; s/ *$//'; }`Apple M4 Pro (14-core)` のような文字列も安全に出力されます。
graph LR
PC["💻 PC 環境<br/>MacBook · macOS 15.x"]
CC["🤖 Claude 環境<br/>plugins 14 · launchd 24"]
PJ["📦 プロジェクト環境<br/>11 repos"]
PC --> CC
CC -->|builds / runs| PJ
PC -.->|ローカル開発| PJClaude環境の内訳はこうなります(実測値)。
graph TD
CC["🤖 Claude Code"]
SK["🧩 Skills"]
SK --> SKa["auto: 77"]
SK --> SKp["plugin: 1004"]
CC --> SK
CC --> AG["🎭 Agents"]
CC --> PL["🔌 Plugins enabled"]
CC --> HK["🪝 Hooks"]
CC --> MCP["🔗 MCP connected"]
CC --> AUTO["⚡ launchd 24 jobs"]<key>StartCalendarInterval</key>
<array>
<dict><key>Hour</key><integer>4</integer><key>Minute</key><integer>50</integer></dict>
<dict><key>Hour</key><integer>8</integer><key>Minute</key><integer>10</integer></dict>
</array>
<key>RunAtLoad</key><false/>4:50に発火するのは、4:55のvault-auto-ingestより前に出力を置くためです。ingestがそのままObsidianへコミットしてくれるのでgit操作が不要。8:10は夜間スリープで4:50がスキップされた場合のキャッチアップです。冪等なので多重発火しても無害。`RunAtLoad: false` でplist読み込み時の即時発火は抑制します。
ログは1本のファイルに集約します。
<key>StandardOutPath</key>
<string>~/.claude/logs/env-map.launchd.log</string>
<key>StandardErrorPath</key>
<string>~/.claude/logs/env-map.launchd.log</string>最後の行に `generated (N lines)` が出ればOKです。
[2026-06-20 04:50:03] environment-map.md generated (218 lines)本稿と死活監視(automation-health-check)を組み合わせると、「何が動いていて・何が壊れていて・何が積み残されているか」の朝のスナップショットが1ページで揃います。
Lily(@bokuwalily)― 個人開発者。Claude Code で自動化基盤を組みながら、iOSアプリやWebサービスを量産しています
◼︎作ったアプリは **ポートフォリオ** にまとめています📱
◼︎新着・開発の裏側は X **@bokuwalily** で発信しています🐦
◼︎OSS: **github.com/bokuwalily**🐙
皆さんの ❤️ やシェアが励みになります!