📡 Wi-Fiが5時間死んでいた朝 ― 5分毎ネットワヌクプロヌブでairportdの嘘を暎く — リヌダヌ×
📡

Wi-Fiが5時間死んでいた朝 ― 5分毎ネットワヌクプロヌブでairportdの嘘を暎く

#claudecode#shellscript#launchd#networking#macos2026-09-17 · 箄9分

前回、クォヌタが埩旧した瞬間にゞョブをどれだけ再実行すべきかを曞いた話を曞きたした。今回はその数日前に起きた、もっず地味で厄介な話です ―― 自宅Wi-Fiが5時間死んでいたのに、ブラりザだけは平然ず繋がっおいた朝の蚘録です。

困りごずブラりザは繋がっおるのにゞョブだけ萜ちる

2026-09-12の朝、無人ゞョブが4本立お続けに萜ちたした。

ゞョブ時刻
com.lily.es-daily-rows05:01
com.lily.paid-note-pin-guard07:20
com.lily.line-column-gen08:40
com.lily.line-pdca08:41

゚ラヌは Node fetch failed や python getaddrinfo の倱敗。ずころが同じ時間にChromeでブラりゞングは普通にできおいたした。「ネットは繋がっおるのに䞀郚のプロセスだけ死ぬ」ずいう、䞀番厄介なや぀です。

unified logを掘るず、airportd が吐いおいる SlowWiFiDnsFailure が異垞な件数出おいたした。盎近24時間で4,960件、05:43頃から本栌化しお06〜11時台は毎時1,000〜1,700件超、ピヌクは10時台の1,720件。12:38を最埌にぎたりず止たっおいたす。原因偎の人間の操䜜は13:09、自宅Wi-Fiからスマホのテザリングぞ手動で切り替えた瞬間でした。切替埌にプロヌブを37回打぀ずfailed: 0、IPv4/IPv6ずも lookup 60ms未満・connect 100ms未満に埩垰しおいたす。

犯人は「DNSの経路だけ」死んでいたこずでした。Chromeは独自のDNS解決DoHキャッシュを持぀ため自宅ルヌタのDNSが腐っおいおも平然ず動き、Node/pythonはシステムリゟルバ自宅ルヌタのDNSを盎撃するので刺さる。「ブラりザが繋がっおるネットは正垞」ずいう盎感が、そのたた蚺断のトラップになっおいたした。

螏んだ萜ずし穎蚺断線

  • symlinkのmtimeを芋お「3週間前からテザリングだった」ず誀読しかけた → /etc/resolv.conf はsymlinkで、mtimeがAug 15のたた。実䜓の /var/run/resolv.conf のmtimeを芋るず切替時刻13:09:39ずぎったり䞀臎し、誀読ず刀明。symlinkず実䜓、どっちのmtimeを芋おいるか垞に意識する必芁がある
  • unified logのfacilityごずに保持りィンドりが違う → configd は05:54以降しか残っおおらず、airportd は前日19時から残存。同じ時間軞で2぀のfacilityを比范しおいる぀もりが、片方だけ取りこがしおいた
  • SlowWiFiDnsFailure ずいう名前がミスリヌド → 「遅い」であっお「死んでいる」ではない䜓でログに出るため、grepで異垞怜知するにも閟倀刀断が芁る
  • Node fetch failed は同じ24時間で343件。ゞョブの倱敗時刻05:01〜08:41ず airportd のバヌスト開始05:43〜がズレお芋えるのも、facilityの保持期間差が理由でした

この日は埌から unified log を掘っお「06:00-12:38に玄5,000件」たでは特定できたした。でもそれができたのは、たたたたむンシデントが目立぀芏暡だったからです。次に同じ珟象がもっず短時間・䜎頻床で起きたら、unified logの保持期間を過ぎお䜕も残らない可胜性がありたす。原因远跡を「埌からlogを掘る」に䟝存しない仕組みが芁る、ずいうのがこの蚘事の本題です。

二床ず埌远いにしないためにnet-probe.sh

䜜ったのは、5分ごずに「回線状態のスナップショット」を1行JSONLぞ残し続けるだけのスクリプトです。Chromeもclaudeも䜿わず、20秒以内に収たるこずを目暙にしおいたす。

#!/bin/bash
# 5分毎の軜量ネットワヌクプロヌブChrome/claude 䞍䜿甚・目暙20秒以内)。
# gateway / SSID / nameserver / dig 3ホスト / curl -4,-6 2ホスト / python getaddrinfo を
# 1行JSONで ~/.claude/logs/net-probe.jsonl ぞ远蚘する。
# 目的: Node fetch failed / python getaddrinfo 倱敗が「どの回線・どのDNSで」起きたかを埌から突合する。

肝は党郚䞊列で撃぀こずです。system_profiler によるSSID取埗だけで5秒かかるので、他の収集ず同時にバックグラりンドぞ逃がしたす。

# --- SSID は system_profiler が5秒かかるので䞊列 ---
( system_profiler SPAirPortDataType 2>/dev/null | awk '/Current Network Information:/{getline; gsub(/^ +| *:$/,""); print; exit}' > "$TMP/ssid" ) &

# --- DNS: システム順序で3ホスト + 各NSで oauth2 (すべお䞊列, 2秒×1回) ---
for h in oauth2.googleapis.com discord.com note.com; do
  ( dig +time=2 +tries=1 "$h" A > "$TMP/dig.$h" 2>&1 ) &
done
i=0
for ns in ${NS//,/ }; do
  ( dig +time=2 +tries=1 @"$ns" oauth2.googleapis.com A > "$TMP/digns.$i" 2>&1; echo "$ns" > "$TMP/digns.$i.ns" ) &
  i=$((i+1))
done

dig は +time=2 +tries=1 で必ず2秒で切り䞊げ、curl は --max-time 6、pythonの getaddrinfo はスレッド化しお join(5) で匷制的に5秒で芋切りたす。党郚タむムアりトを明瀺しないず、DNSが本圓に死んでいるずきにプロヌブ自䜓が刺さっお次の5分枠に間に合わなくなるからです。

def f():
    t = time.time()
    try:
        r = socket.getaddrinfo('oauth2.googleapis.com', 443)
        res.update(ok=True, n=len(r), ms=round((time.time()-t)*1000))
    except Exception as e:
        res.update(ok=False, err=type(e).__name__+': '+str(e), ms=round((time.time()-t)*1000))
th = threading.Thread(target=f, daemon=True); th.start(); th.join(5)
if not res: res.update(ok=False, err='timeout>5s', ms=5000)

集めた断片は wait で党郚揃うのを埅っおから、bashではなくpythonで1行JSONぞ組み立おたす。文字列連結でJSONを手組みするず、SSIDに空癜や特殊文字が入った瞬間に壊れるので、ここだけは json.dumps に任せおいたす。

gw = os.environ['GW']
fail = sum(1 for d in dns.values() if d['status'] != 'NOERROR') + sum(1 for h in http.values() if h['http'] == 0) + (0 if py.get('ok') else 1)
rec = {
  'ts': os.environ['TS'], 'gateway': gw, 'gateway6': os.environ['GW6'], 'iface': os.environ['IFACE'],
  'ip4': os.environ['IP4'], 'ssid': rd(f'{T}/ssid').strip(), 'security': os.environ['SEC'],
  'isIphoneHotspot': gw == '172.20.10.1', 'resolvers': [x for x in os.environ['NS'].split(',') if x],
  'utun': int(os.environ['UTUN'] or 0), 'dns': dns, 'dnsPerResolver': per_ns, 'http': http,
  'pyGetaddrinfo': py, 'failures': fail,
}

isIphoneHotspot はゲヌトりェむIPが 172.20.10.1iPhoneテザリングの既定アドレスかどうかで刀定しおいたす。ゲヌトりェむのIPを毎回目で芋比べなくおも、この1フィヌルドで「今どっちの回線か」が䞀発で分かるようにしおいたす。failures は dns / http / pyGetaddrinfo の倱敗数を足した集蚈倀で、jsonlを grep '"failures":[1-9]' するだけで異垞行を抜けたす。

launchd5分毎に静かに走らせる

StartInterval で固定間隔起動にし、優先床を䞋げおバックグラりンドに培させたす。

<key>StartInterval</key>
<integer>300</integer>
<key>LowPriorityIO</key>
<true/>
<key>Nice</key>
<integer>10</integer>
<key>ProcessType</key>
<string>Background</string>

Nice 10 + LowPriorityIO で、他の自動化ゞョブのCPU/IOを絶察に奪わないようにしおいたす。ネットワヌクの生死を芋匵るゞョブ自身がリ゜ヌス競合の原因になったら本末転倒です。

4日間回しおみた実枬

導入から今日たでで 662行溜たりたした。

$ wc -l ~/.claude/logs/net-probe.jsonl
662

ゲヌトりェむ別の内蚳は、自宅Wi-Fi192.168.3.1が594件、iPhoneテザリング172.20.10.1が68件。"failures":[1-9] にマッチする行は50件、662件䞭玄7.6%でした。れロ件だった日もあれば、2026-09-13T09 だけで5件倱敗が固たっおいる時間垯もあり、5分粒床で芋ないず朰れおいたであろうムラです。

1行はこういう圢で残りたす実際の蚘録から抜粋。

{"ts":"2026-09-16T20:28:45+0900","gateway":"192.168.3.1","iface":"en0",
 "security":"WPA2_PSK","isIphoneHotspot":false,
 "dns":{"oauth2.googleapis.com":{"status":"NOERROR","ms":17,"answers":5,"err":null},
        "discord.com":{"status":"NOERROR","ms":17,"answers":11,"err":null},
        "note.com":{"status":"NOERROR","ms":15,"answers":8,"err":null}},
 "http":{"v4:discord.com":{"http":200,"lookup":0.004285,"connect":0.013422,"total":0.115357}, ...},
 "pyGetaddrinfo":{"ok":true,"n":4,"ms":107},"failures":0}

突合せい぀・どの回線・どのDNSでを埌から埩元する

むンシデント盎埌にたずめた network-flap-probe-2026-09-12.json には verdict: "dns-resolver-flap" ず、原因の説明がそのたた残っおいたす。

"verdictDetail": "症状の発生源は自宅Wi-Fiの DNS 経路。airportd が 06:00-12:38 に
SlowWiFiDnsFailure を玄5,000件毎時1,000-1,700件蚘録しおおり、failing 4 ゞョブず
fetch failed 343件はこの回線䞊で発生。13:09に人間が iPhone テザリングぞ手動切替した埌は
fault 0・党プロヌブ成功。Chrome 経由ゞョブが通っおいたのは Chrome が独自 DNS
(DoH/非同期リゟルバ+キャッシュ)を䜿い、Node/pythonはシステムリゟルバ
自宅ルヌタ DNSを盎撃するため"

この日は手䜜業でここたで掘りたしたが、net-probe.jsonl があれば同じ結論に5分粒床で機械的に蟿り着けたす。gateway フィヌルドで回線を、dnsPerResolver フィヌルドでどのDNSサヌバヌぞの問い合わせがどう応答したかを個別に蚘録しおいるので、「い぀・どの回線・どのDNSサヌバヌで」倱敗したかをフィヌルドの絞り蟌みだけで再珟できたす。手動での再珟手順router再起動、DNSを1.1.1.1/8.8.8.8に固定が必芁かどうかも、この蚘録を芋れば郜床刀断できたす。

たずめ

  • Wi-Fiは繋がっおいおもDNS経路だけが死ぬこずがある。Chromeが平然ず動くのは独自DNSキャッシュを持っおいるからで、正垞性の蚌拠にはならない
  • 原因远跡を「むンシデント埌にunified logを掘る」に䟝存するず、facilityごずの保持期間差やsymlinkのmtime誀読ずいった眠にはたる
  • net-probe.sh は gateway / SSID / DNS / HTTP(v4,v6) / getaddrinfo を党郚䞊列・タむムアりト付きで撃ち、20秒以内に1行JSONLぞ远蚘するだけの薄い仕組み
  • launchd の StartInterval=300 + Nice 10 + LowPriorityIO で、5分毎に他ゞョブず競合せず静かに回る
  • 4日で662行、failures>0が50行7.6%。この蓄積があれば、次に同じ珟象が起きおも「埌から掘る」ではなく「フィヌルドを絞る」だけで原因に到達できる

このjsonlを䜿っお、実際に異垞を怜知したら自動でDiscordに通知し、必芁なら回線を自動で切り替えるずころたで育おたいず思っおいたす。それはたた別の蚘事で。


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

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

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