先に結論だけ(すべて自宅の検証機での実測。会社支給の端末は使っていません)

  • 同じクラッシュでも、見る場所で数字が変わる: cmd・PowerShell・schtasks は -1073741819、subprocess と Get-ScheduledTaskInfo は 3221225477。16 進ではどちらも 0xC0000005(2 章)
  • 0xC で始まる終了コードは Python の例外ではない: stderr は空。手掛かりは faulthandler とイベント ID 1000(3 章・4 章)
  • ID 1000 は起動元によっては残らない: Git Bash から起動したクラッシュは 0 件(6.3)
  • pythonw に PYTHONFAULTHANDLER=1 を付けると、スクリプトが 1 行も動かない(6.1)
  • 対策は標準ライブラリだけの 93 行: 例外・クラッシュ・終了コードを 3 つのファイルに残す(5 章)

▶ まずやること: 終了コードを 16 進 8 桁に直し、図 1 で次に見る場所を決める。

この記事の目次

1. ログも例外も無く消えた — この記事で扱う「無言終了」

※ 次の段落は説明のための架空の例です。

タスクスケジューラで回していたデータ収集スクリプトの出力が、いつの間にか止まっていた。ログの最終行はいつもの INFO で、Traceback は無い。schtasks で前回の結果を見ると -1073741819。検索しても「IDE を入れ直したら直った」という報告ばかりで、原因に近づけない。

本記事では、例外の記録を残さずにプロセスが終わることを「無言終了」と呼びます。中身は 3 種類です。

  • 例外は起きたが、出力先が無くて見えない(pythonw・--noconsole の exe・別スレッド)
  • そもそも Python の例外にならない(C 拡張や DLL の中でのアクセス違反など)
  • 外から止められた(taskkill /F・Ctrl+C など)

どれに当たるかを最初に教えてくれるのが終了コード(プロセスが終わるときに OS へ返す数値。0 が正常)で、トレースバックが出せない死に方でも残ります。exe が起動しない症状は PyInstaller の逆引きガイド、本記事は「動いていたのに消えた」側です。

2. 終了コードの読み方 — 5 か所で読むと 2 通りに見える

同じクラッシュの終了コードを、5 つの場所から読みました(Python 3.12.10・python.exe)。

見る場所アクセス違反os.abort()C 関数をはさむ再帰未捕捉の KeyboardInterrupt未捕捉の例外
cmd の %ERRORLEVEL%・PowerShell 5.1 の $LASTEXITCODE-1073741819-1073740791(未計測)-10737415101
schtasks /Query /V の Last Result-1073741819-1073740791-1073741571-10737415101
subprocess.run().returncode・Get-ScheduledTaskInfo の LastTaskResult32212254773221226505322122572532212257861
16 進(NTSTATUS の名前)0xC0000005
ACCESS_VIOLATION
0xC0000409
STACK_BUFFER_OVERRUN
0xC00000FD
STACK_OVERFLOW
0xC000013A
CONTROL_C_EXIT
0x1

NTSTATUS は Windows の 32 ビットの状態コードで、先頭が 0xC のものはエラーです。KeyboardInterrupt の列は raise KeyboardInterrupt を捕まえずに終えた場合です。

2.1 負の数と 3221225477 は同じもの

違いは同じ 32 ビットの値を符号付きで読むか、符号なしで読むかだけで、符号付きに 4,294,967,296 を足すと符号なし(-1073741819 → 3221225477)、16 進 8 桁で書くと NTSTATUS(0xC0000005)です。検索は 16 進で行ってください。

# PowerShell(直前のコマンドの終了コード / タスクの前回の結果)
'0x{0:X8}' -f $LASTEXITCODE
Get-ScheduledTaskInfo -TaskName "タスク名" | ForEach-Object { '0x{0:X8}' -f $_.LastTaskResult }

# Python(r は subprocess.run(...) の結果)
print(f"0x{r.returncode & 0xFFFFFFFF:08X}")

2.2 タスクスケジューラと、自作ランチャーの注意

タスクスケジューラの記事の戻り値コード表には 0xC 系が無いので、上の表がその続きです(GUI の「前回の実行結果」列の表示は未採取)。

子の終了コードを親の Python で返すとき、sys.exit(3221225477) は OverflowError: Python int too large to convert to C long になり、0xFFFFFFFF(-1)に化けました。sys.exit(-1073741819) と符号付きにすれば 0xC0000005 のままです。

終了コードから次に見る場所を決める切り分け図 終了コード 8 種について、意味と次に見る場所を 1 行ずつ並べた表形式の図。 終了コード(16 進 8 桁)から、次に見る場所を決める 終了コード 意味(検証機で再現した終わり方) 次に見る場所 0xC0000005 -1073741819 アクセス違反 C 拡張・ctypes・DLL の中で落ちた fault.log(4.2)と ID 1000 の障害モジュール名(4.3) 0xC0000409 -1073740791 STACK_BUFFER_OVERRUN 検証機では os.abort() でもこのコード ID 1000(検証機では ucrtbase.dll) 名前だけでバッファあふれと決めない 0xC00000FD -1073741571 スタックオーバーフロー C 関数をはさむ深い再帰(3.12.10) 再帰の途中に sorted の key や コールバックが入っていないか(3.2) 0xC000013A -1073741510 Ctrl+C 未捕捉の KeyboardInterrupt 誰が Ctrl+C を送ったか (.bat の確認表示・3.3) 0xFFFFFFFF -1 外から強制終了 Stop-Process -Force など 止めた側(運用者・監視の仕組み) 自作ランチャーの符号ミスも疑う(2.2) 0x00000003 3 os._exit(3) faulthandler 有効時の os.abort() も 3 fault.log に Fatal Python error: Aborted があるか 0x00000001 1 未捕捉の例外・sys.exit(1) taskkill /F・Popen.terminate() も 1 トレースバック(crash.log) 無ければ強制終了を疑う 0x00000000 0 正常終了 別スレッドが例外で死んでいても 0 crash.log の THREAD 行 (収集スレッドだけ止まっていないか) 赤枠の 3 つは Python の例外ではない(stderr は空)。1 と 0 だけで例外の有無を決めない。
図 1: 終了コード別の「次に見る場所」(Python 3.12.10 で再現)。crash.log・fault.log は 5 章のコードが作ります。スマートフォンでは図を横にスクロールできます。

3. 無言終了の 3 種類 — 終了コードと「残るもの」

終わり方を 12 通り作り、終了コードと残ったものを並べました(3.12.10 の python.exe を PowerShell 配下の subprocess から起動)。ID 1000 はイベントログのクラッシュ記録です(4.3)。

終わり方returncode16 進stderr に残ったものID 1000
未捕捉の例外10x1トレースバック0 件
sys.exit("fatal: config not found")10x1メッセージ 1 行—
sys.exit(2)20x2なし—
os._exit(3)30x3なし(atexit も走らない)0 件
os.abort()32212265050xC0000409なし1 件
アクセス違反(3.2 の方法)32212254770xC0000005なし1 件
C 関数をはさむ深い再帰32212257250xC00000FDなし1 件
未捕捉の KeyboardInterrupt32212257860xC000013Aトレースバック0 件
別スレッドの未捕捉例外00x0トレースバック(メインは継続)—
Popen.terminate()10x1—0 件
taskkill /F /PID10x1—0 件
Stop-Process -Force-1(.NET の表示)0xFFFFFFFF—0 件

ID 1000 の「—」は未集計、強制終了の 3 行は PowerShell から止めた結果です。

終了コード 1 は例外とは限りません(taskkill /F でも 1)。終了コード 0 でも安心できません(別スレッドが死んでもメインが走り切れば 0)。

3.1 例外は起きたが、出力先が無い

pythonw.exe や --noconsole の exe では sys.stderr が None で、トレースバックの行き先がありません(既存記事で実測済み: タスクスケジューラ 6 章・tkinter 監視アプリ 6 章・logging 設計 7 章)。

3.2 OS レベルのクラッシュ — 0xC0000005・0xC0000409・0xC00000FD

0xC0000005(アクセス違反)は、触れてはいけないメモリに触れたときのコードです。純 Python の間違いは原則として例外になるので、疑うのは C 拡張や ctypes で呼ぶ DLL など C の部分です。

faulthandler の公式ドキュメントが Linux でのクラッシュ例に挙げる ctypes.string_at(0) は、検証機の Windows では落ちませんでした(実行結果)。

start mode=string_at0
caught OSError: exception: access violation reading 0x0000000000000000
still alive
end

ctypes のドキュメントによると、Windows の ctypes は外部関数の呼び出し中のアクセス違反を構造化例外処理(SEH)で OSError に変えます。この安全網の外で不正なポインタを読むと即死し、検証ではこの 1 行で再現しました。

import ctypes
ctypes.cast(1, ctypes.py_object).value   # 不正な非 NULL ポインタを Python オブジェクトとして読む

⚠️ 検証専用のコードです。実行したプロセスは保存処理をせずに即死します。保存していない作業を閉じてから、設備に接続していない検証用の PC で試してください。

stderr は空で、ID 1000 の障害モジュールは _ctypes.pyd でした。

0xC0000409 の名前は STACK_BUFFER_OVERRUN ですが、検証機では os.abort() でもこのコードになり、障害モジュールは ucrtbase.dll(C ランタイム)でした。Microsoft の __fastfail による即時終了もこのコードで現れるので、名前だけで決めつけないでください(5 章のコードの「fast-fail」は目安のラベル)。Python の公式ドキュメントの「os.abort() は Windows では終了コード 3」は、実測ではfaulthandler が有効なときだけで、無効なら 0xC0000409でした。faulthandler の SIGABRT ハンドラーがあると、UCRT の abort はそちらを呼んで WER を経ずに 3 で終わるためです(Microsoft Learn)。

0xC00000FD(スタックオーバーフロー)は、3.12.10 の純 Python の再帰では上限 100 万でも出ず RecursionError で止まり、落ちたのは再帰の途中に C で書かれた関数をはさんだときです。

"""再帰の途中に C 関数(sorted の key)を挟む。main スレッドで実行"""
import sys
def viaC(n):
    if n % 100 == 0:
        print(f"depth={n}", flush=True)
    return sorted([0], key=lambda x: viaC(n + 1))
try:
    viaC(0)
except RecursionError as e:
    print(f"RecursionError: {e}", flush=True)
print("end", flush=True)

3.12.10 では depth=300 の表示を最後に 0xC00000FD で即死し、stderr は空でした。3.14.7 では例外として止まりました(別スレッドでも同じ結果。実行結果・該当部分)。

RecursionError: Stack overflow (used 2912 kB) while calling a Python object
end

3.3 os._exit・Ctrl+C・外からの停止

os._exit(n) は後始末をせずに終わり、ファイルバッファの書き出しも走りません。

未捕捉の KeyboardInterrupt は 0xC000013A で、.bat の中で子がこのコードで終わると Terminate batch job (Y/N)? が表示されました。

taskkill /F・Popen.terminate()・Stop-Process -Force で止められたプロセスは何も書けず、手掛かりは止めた側(運用者の操作・監視の仕組み・タスクの実行時間の上限=0x41306)にしかありません。

4. 原因を残す 3 段構え — excepthook・faulthandler・イベントログ

3 章の終わり方は記録できる場所が違うので、プロセスの内側に 2 段、外側に 1 段を重ね、親プロセスで終了コードも残します。

死因を残す 3 段構えの図 プロセス内の excepthook と faulthandler、外側のイベント ID 1000、親のランチャーの exit.log が、それぞれどの終わり方を記録するかを示す図。 死因を残す 3 段構え — どの終わり方を、どこが拾うか あなたの Python プロセス(python.exe・pythonw.exe・exe) 1 段目 sys / threading の excepthook Python の例外(終了コード 1) 別スレッドの例外(終了コードは 0 のまま) 2 段目 faulthandler(ファイル出力) 例外にならない死 0xC0000005・0xC00000FD・abort 印 atexit 後始末まで来たら「atexit reached」を書く クラッシュ・os._exit・強制終了では書かれない crash.log fault.log プロセスの外側(Windows) 3 段目 イベント ID 1000 障害モジュール名・例外コード クラッシュ 3 種だけが残る WER のダンプ(任意) 既定では無効・管理者権限が要る 会社 PC は情シスと相談 起動元しだいで記録されない (Git Bash 配下で 0 件・6.3) 親プロセス(ランチャー) 子を起動して待ち、終了コードを 16 進と意味つきで exit.log に書く(どんな終わり方でも残る) どこにも書けない os._exit・taskkill /F は、exit.log の終了コードと 「fault.log に start 行だけ・crash.log に atexit 行なし」から推定する(5.3)
図 2: 1・2 段目はプロセスの中で動くので、外から止められると何も書けません。スマートフォンでは図を横にスクロールできます。

4.1 1 段目: excepthook — Python の例外

sys.excepthook と threading.excepthook を差し替え、未捕捉の例外をファイルに書きます。logging 設計の記事 7 章のフックと併用すると後から代入したほうが勝つので、どちらかに寄せてください。

4.2 2 段目: faulthandler — 例外にならない死

faulthandler は、落ちる直前に Python のスタックを書き出す標準ライブラリです(-X faulthandler・PYTHONFAULTHANDLER=1・faulthandler.enable(file=...) で有効化)。-X faulthandler を付けると、空だった stderr に次の内容が出ました(実行結果)。

Windows fatal exception: access violation

Current thread 0x00008434 (most recent call first):
  File "C:\work\silent_exit\fh.py", line 7 in crash
  File "C:\work\silent_exit\fh.py", line 54 in <module>

どの行で落ちたかが分かれば、その行が呼ぶライブラリが容疑者です。

enable(file=...) ではファイルを開いたままにする必要があります。with で閉じたあとに落とすと何も残らず、閉じたあとに別のファイルを開いてから落とすとその別のファイルに書かれました。faulthandler はファイル記述子の番号で書くためです(公式ドキュメントにも同趣旨の注意)。

固まる症状には faulthandler.dump_traceback_later(秒数, exit=True, file=f) が使え、3 秒指定で 30 秒眠る関数を動かすと、3 秒後のスタックを残して終了コード 1 で終わりました(実行結果)。

Timeout (0:00:03)!
Thread 0x00005a08 (most recent call first):
  File "C:\work\silent_exit\fh.py", line 34 in stuck
  File "C:\work\silent_exit\fh.py", line 35 in <module>

4.3 3 段目: イベントビューアー(Application・ID 1000)

OS レベルのクラッシュは、イベントログ(Application・ソース Application Error・ID 1000)にも残ります。PowerShell なら直近 1 日分をこう引けます。

Get-WinEvent -FilterHashtable @{
    LogName      = 'Application'
    ProviderName = 'Application Error'
    Id           = 1000
    StartTime    = (Get-Date).AddDays(-1)
} -ErrorAction SilentlyContinue | Format-List TimeCreated, Message

検証機に残った本文の先頭です(実行結果・先頭 4 行)。

障害が発生しているアプリケーション名: pythonw.exe、バージョン: 3.12.10150.1013、タイム スタンプ: 0x67f515a8
障害が発生したモジュール名: _ctypes.pyd、 バージョン: 3.12.10150.1013、タイム スタンプ: 0x67f5157c
例外コード: 0xc0000005
フォールト オフセット: 0x0000000000004168

見るのは「障害が発生したモジュール名」で、検証ではアクセス違反が _ctypes.pyd、os.abort() が ucrtbase.dll、C 関数をはさむ再帰が python312.dll でした。ただし ntdll.dll など OS のモジュールは、他に壊された「被害者」のこともあります(Microsoft の文書)。

記録されたのは 3 章のクラッシュ 3 種だけで(pythonw.exe・タスクからの起動も記録)、未捕捉例外・KeyboardInterrupt・os._exit()・強制終了は 0 件でした。非公開 API の faulthandler._read_null() は内部でクラッシュ報告を抑えるので、0xC0000005 で落ちてもID 1000 が残りません。基本は exe-not-running の記事 2.5、本記事から足すのはID 1000 が無いことはクラッシュしていない証拠にならないという点です(6.3)。

4.4 さらに深く: WER のローカルダンプ

最後の手段は、クラッシュ時のメモリを保存する Windows エラー報告(WER)の LocalDumps です。Microsoft Learn「Collecting User-Mode Dumps」によると既定では無効で、設定(下のコマンドの HKLM のキー)には管理者権限が要り、既定は最大 10 個のミニダンプ(DumpType=2 でフルダンプ)です。検証機では別のアプリが作ったと見られるキーが値未設定のまますでにあり、本記事のクラッシュのミニダンプ(1 個 1.58〜2.00 MB)が10 個で頭打ち・古いものから上書きで残っていました。読み取りだけの確認は次の 2 行です。

Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps'
Get-ChildItem "$env:LOCALAPPDATA\CrashDumps"

⚠️ 会社の PC で LocalDumps を設定する前に、情報システム部門に相談してください。レジストリの変更には管理者権限が要り、端末の管理方針に関わります。フルダンプにはプロセスのメモリ内容がそのまま入るので、通信中のデータや認証情報が含まれえます。ダンプファイルを社外へ送らないでください。

5. 死因をファイルに残す run_with_crash_log.py(93 行)

4 章の 1 段目・2 段目と終了コードの記録を、標準ライブラリだけの 1 ファイルにまとめました。pip が使えない閉域網の PC にもコピーで持ち込め、管理者権限も要りません。

  • アプリ側: 先頭で install() を呼ぶと crash.log(例外と atexit の印)と fault.log ができる(exe でも同じ)
  • 起動側(ランチャー): このファイルからアプリを子として起動し、終了コードを 16 進と意味つきで exit.log に残してそのまま返す
"""run_with_crash_log.py — 無言終了の「死因」をファイルに残す(標準ライブラリのみ)

使い方 1(アプリ側): main の先頭で install() を呼ぶ。exe(PyInstaller)でも同じ。
    import run_with_crash_log
    run_with_crash_log.install("logs")
使い方 2(起動側): 子プロセスとして起動し、終了コードを 16 進と意味で記録する。
    python run_with_crash_log.py logs python app.py
"""
import atexit
import datetime
import faulthandler
import os
import subprocess
import sys
import threading
import traceback

# 終了コード(符号なし 32 ビット)→ 意味。よく見るものだけ
KNOWN_CODES = {
    0x00000000: "正常終了",
    0x00000001: "Python の未捕捉例外 / sys.exit(1) / taskkill /F などの強制終了",
    0x00000003: "os._exit(3) / faulthandler 有効時の os.abort()",
    0xFFFFFFFF: "Stop-Process -Force などの強制終了(-1)",
    0xC0000005: "アクセス違反(C 拡張・ctypes・DLL の中で落ちた)",
    0xC00000FD: "スタックオーバーフロー(C 関数をはさむ深い再帰など)",
    0xC0000409: "fast-fail(os.abort() や C ランタイムの abort() など)",
    0xC000013A: "Ctrl+C / コンソールを閉じた(KeyboardInterrupt 未捕捉を含む)",
    0xC0000374: "ヒープ破損",
}

_fault_file = None  # faulthandler の出力先。閉じると記録されなくなるので保持し続ける


def _now():
    return datetime.datetime.now().isoformat(timespec="seconds")


def _write(path, text):
    with open(path, "a", encoding="utf-8") as f:
        f.write(f"{_now()} pid={os.getpid()} {text}\n")


def install(log_dir="logs"):
    """excepthook 2 つと faulthandler をまとめて仕掛ける"""
    global _fault_file
    os.makedirs(log_dir, exist_ok=True)
    crash_log = os.path.join(log_dir, "crash.log")

    def on_exception(exc_type, exc, tb):  # 1 段目: メインスレッドの未捕捉例外
        _write(crash_log, "UNCAUGHT\n" + "".join(traceback.format_exception(exc_type, exc, tb)))
        if sys.stderr is not None:          # pythonw / --noconsole では None
            sys.__excepthook__(exc_type, exc, tb)

    def on_thread_exception(args):          # 1 段目(続き): 別スレッドの未捕捉例外
        name = args.thread.name if args.thread else "?"
        _write(crash_log, f"THREAD {name}\n"
               + "".join(traceback.format_exception(args.exc_type, args.exc_value, args.exc_traceback)))

    sys.excepthook = on_exception
    threading.excepthook = on_thread_exception

    # 2 段目: 例外にならない死(0xC0000005 など)。追記モードで開き、閉じずに持つ
    _fault_file = open(os.path.join(log_dir, "fault.log"), "a", encoding="utf-8")
    _fault_file.write(f"--- {_now()} pid={os.getpid()} start {sys.argv}\n")
    _fault_file.flush()
    faulthandler.enable(file=_fault_file, all_threads=True)

    # 正常な後始末まで来たら印を残す(os._exit・強制終了・クラッシュでは書かれない)
    atexit.register(_write, crash_log, "atexit reached")


def describe(code):
    u = code & 0xFFFFFFFF
    return f"{code} (0x{u:08X}) {KNOWN_CODES.get(u, '不明(NTSTATUS 一覧で 16 進を検索)')}"


def launch(log_dir, cmd):
    """子プロセスとして起動し、終了コードを exit.log に 1 行で残す"""
    os.makedirs(log_dir, exist_ok=True)
    exit_log = os.path.join(log_dir, "exit.log")
    _write(exit_log, f"START {cmd}")
    rc = subprocess.run(cmd).returncode
    _write(exit_log, f"EXIT {describe(rc)}")
    return rc


if __name__ == "__main__":
    if len(sys.argv) < 3:
        print("usage: python run_with_crash_log.py LOG_DIR COMMAND [ARGS...]")
        sys.exit(2)
    rc = launch(sys.argv[1], sys.argv[2:])
    # 子の終了コードをそのまま返す。3221225477 のままだと OverflowError になるので符号付きへ
    sys.exit(rc - (1 << 32) if rc >= (1 << 31) else rc)

要所は、_fault_file を閉じずに持ち続けること(4.2)、sys.stderr が None なら元のフックを呼ばないこと(pythonw 対策)、子の終了コードを符号付きで返すこと(2.2)です。

5.1 動かし方

動作確認に使った検証用アプリです。av と abort はプロセスを即死させるので、3.2 の注意を守ってください。

"""検証用アプリ: install() を呼んだうえで、引数のモードで落ちる"""
import sys, os, ctypes, threading, time
import run_with_crash_log
run_with_crash_log.install(os.environ.get("LOGDIR", "logs"))
mode = sys.argv[1] if len(sys.argv) > 1 else "ok"
print(f"demo start mode={mode}", flush=True)
if mode == "uncaught":
    1 / 0
elif mode == "thread":
    t = threading.Thread(target=lambda: int("x"), name="collector"); t.start(); t.join()
elif mode == "av":
    ctypes.cast(1, ctypes.py_object).value
elif mode == "os_exit":
    os._exit(3)
elif mode == "abort":
    os.abort()
print("demo end", flush=True)

2 つを同じフォルダに置き、ランチャー経由で起動します(第 1 引数がログフォルダ、以降が子のコマンド)。

python run_with_crash_log.py logs python demo_app.py av

ログフォルダは絶対パスで指定してください(install() に渡すフォルダも同じ)。タスクスケジューラ起動では作業フォルダが System32 になります(タスクスケジューラの記事 4 章)。

5.2 実際に残ったログ

PowerShell 5.1 から 6 通りの落とし方で起動すると、ランチャーの終了コードは ok 0・uncaught 1・thread 0・av -1073741819・os_exit 3・abort 3(faulthandler 有効のため)で、子の値を引き継ぎました。exit.log の av と os_exit の行です(実行結果・抜粋)。

2026-10-09T15:39:49 pid=16484 START ['C:\\Users\\<USER>\\AppData\\Local\\Programs\\Python\\Python312\\python.exe', 'demo_app.py', 'av']
2026-10-09T15:39:52 pid=16484 EXIT 3221225477 (0xC0000005) アクセス違反(C 拡張・ctypes・DLL の中で落ちた)
2026-10-09T15:39:52 pid=21640 START ['C:\\Users\\<USER>\\AppData\\Local\\Programs\\Python\\Python312\\python.exe', 'demo_app.py', 'os_exit']
2026-10-09T15:39:52 pid=21640 EXIT 3 (0x00000003) os._exit(3) / faulthandler 有効時の os.abort()

fault.log の av 以降です(実行結果・抜粋)。crash.log には uncaught のトレースバックと atexit 行が残りました。

--- 2026-10-09T15:39:49 pid=35648 start ['demo_app.py', 'av']
Windows fatal exception: access violation

Current thread 0x00001070 (most recent call first):
  File "C:\work\silent_exit\demo_app.py", line 12 in <module>
--- 2026-10-09T15:39:52 pid=2712 start ['demo_app.py', 'os_exit']
--- 2026-10-09T15:39:52 pid=22924 start ['demo_app.py', 'abort']
Fatal Python error: Aborted

Current thread 0x00006c64 (most recent call first):
  File "C:\work\silent_exit\demo_app.py", line 16 in <module>

exit.log の pid はランチャー、crash.log・fault.log の pid はアプリのものなので、突き合わせは時刻で行ってください。

pythonw.exe の直接起動と PyInstaller 6.22.3 の --noconsole の exe(onedir・onefile 各 1 本。ほかの 2 本は Smart App Control がブロック。対処はコード署名の記事)でも、ログは同じ形で残りました。

5.3 ログの読み方 — 「何が無いか」も手掛かりになる

fault.log(その pid)crash.log終了コード読み方
start → Windows fatal exception: …atexit なし0xC0000005 などネイティブのクラッシュ。最後の File 行と ID 1000 の障害モジュールを見る
start → Fatal Python error: Abortedatexit なし3os.abort() か、C 側での abort
start だけUNCAUGHT + atexit1Python の未捕捉例外
start だけTHREAD + atexit0別スレッドだけが死に、メインは走り切った
start だけatexit なし3 などos._exit()。強制終了も同じ形になるはず(未確認)
start だけatexit だけ0正常終了

4 行目は、タスク上は成功なのに収集スレッドが止まっている、いちばん気づきにくい形です。

6. 落とし穴 — 記録が当てにならない 4 つの場面

6.1 pythonw に PYTHONFAULTHANDLER=1 を付けると、1 行も動かない

Start-Process で標準ハンドルを渡さずに pythonw.exe を起動し、-X faulthandler か PYTHONFAULTHANDLER=1 を付けると、スクリプトが 1 行も実行されずに終了コード 1 で終わりました。file 省略の faulthandler.enable() は RuntimeError: sys.stderr is None になり、enable(file=開いたままのファイル) なら pythonw でもダンプが残ります。

パイプ付きの subprocess から起動すると sys.stderr が None にならず、問題なく動きました。起動のしかたで結果が変わるので、PYTHONFAULTHANDLER は全体設定に使わず、python.exe で調べるときだけ付けるのが安全です。

6.2 exe の未捕捉例外は「落ちずに止まる」

--noconsole の exe で未捕捉例外が起きると、crash.log に UNCAUGHT が書かれたあと PyInstaller の Unhandled exception in script ダイアログが出て、閉じるまでプロセスが残りました。プロセス監視では正常に見えるので、処理結果の更新時刻も見張ってください(exe-not-running の記事 3.2)。

6.3 Git Bash から起動すると ID 1000 が残らない

3 章のクラッシュを含む 7 ケースを Git Bash から起動すると、ID 1000 は1 件も記録されませんでした(PowerShell からは 3 件)。起動された python.exe のエラーモード(GetErrorMode())は、Git Bash 配下が 0x3、PowerShell 配下が 0x8001 でした。

0x3 には SEM_NOGPFAULTERRORBOX(0x2)が含まれ、Microsoft Learn の SetErrorMode の説明では、このフラグがあると Windows エラー報告が呼ばれず、子プロセスは親のエラーモードを引き継ぎます。次の 1 行の結果に0x2 のビットが含まれていれば、その起動経路では ID 1000 を当てにできません。

python -c "import ctypes; print(hex(ctypes.windll.kernel32.GetErrorMode()))"

6.4 「Windows fatal exception」が出ても死んでいないことがある

faulthandler を有効にして 3.2 の ctypes.string_at(0) を実行すると、ctypes が例外に変えて生き延びた(終了コード 0)のに、stderr には Windows fatal exception: access violation とスタックが出ました(faulthandler が安全網より先に呼ばれるため)。fault.log にこの行があっても死因と決めず、同じ pid の atexit 行が crash.log にあるかを見てください。逆に、SAC にブロックされた exe はログ自体が作られず、「ログが空」には「起動していない」も含まれます。

7. よくある質問(FAQ)

「Process finished with exit code -1073741819 (0xC0000005)」と出ました。何から調べればよいですか

Python の例外ではなく、アクセス違反で落ちたことを示す終了コードです(0xC0000005 の符号付き表示)。入れ直しの前に、python -X faulthandler で落ちた行を出し、ID 1000 の「障害が発生したモジュール名」を見て、C 拡張や DLL の当たりを付けてください(4.2・4.3)。

-1073740791(0xC0000409)はバッファオーバーランという意味ですか

名前は STACK_BUFFER_OVERRUN ですが、当サイトの検証機では os.abort() でもこのコードになりました。名前で決めつけず、ID 1000 の障害モジュール(検証機では ucrtbase.dll)を見てください(3.2)。

再帰で 0xC00000FD になるときと、RecursionError で止まるときの違いは何ですか

当サイトの検証機の 3.12.10 では、純 Python の再帰は RecursionError で止まり、sorted の key のような C 関数をはさんだときだけ 0xC00000FD で即死しました(3.14.7 では RecursionError・3.2)。

PYTHONFAULTHANDLER=1 を環境変数に設定しておけば安心ですか

pythonw.exe には危険です。当サイトの検証機では標準ハンドルなしの pythonw.exe に付けるとスクリプトが 1 行も動かなかったので、コード内で faulthandler.enable(file=開いたままのファイル) を呼んでください(6.1)。

イベントビューアーに ID 1000 が無ければ、クラッシュではないと言えますか

言えません。当サイトの検証機では Git Bash から起動したクラッシュの ID 1000 が 1 件も残らなかったので(6.3)、終了コードを親プロセス側で記録してください(5 章)。

8. 検証環境と、当サイトが測れなかったこと

実測した実測していない(当サイトで未確認という意味)
終了コードの 5 か所での見え方(一時タスクは確認後に削除)タスクスケジューラ GUI の「前回の実行結果」列の表示。0xC00000FD の cmd・PowerShell での表示
3 章の 12 通りの終わり方0xC0000374(ヒープ破損)・DLL 系(0xC0000135 / 0xC0000142)、実在の C 拡張のバージョン不一致、コード中の「コンソールを閉じた」
faulthandler(3.12.10・3.14.7)、ID 1000(PowerShell・Git Bash・タスク・pythonw)WER LocalDumps の有効化・ダンプの解析(レジストリは変更していない)
5 章のコード(ランチャー・pythonw・--noconsole の exe 2 本)タスク登録での通し確認、taskkill /F 時のログ、Windows サービス・SYSTEM アカウント、他の PC・Windows 10

検証環境: 自宅の検証機(Windows 11 Pro 25H2・ビルド 26200・Smart App Control On)。会社支給の端末は使っていません。 Python 3.12.10(64bit)と比較用の 3.14.7、導入済みの PyInstaller 6.22.3。PowerShell 5.1・cmd・Git Bash。標準ライブラリのみで、レジストリ・システム設定の変更なし、管理者権限なし。ログ中のパスは C:\work\silent_exit、ユーザー名は <USER> に置き換えています。計測日は 2026 年 10 月 9 日です。

⚠️ 実機適用時の注意: クラッシュ再現コードは稼働中の設備に接続した PC では実行しないでください。記録の仕組みを現場 PC へ入れるときは、①設備保全部門・情報システム部門の承認を得る、②24 時間稼働している PLC や設備には接続せず、計画停止中か読み出し専用の検証環境で確かめる、③安全インターロック・安全 PLC・法定の警報装置は Python アプリで置き換えない、④レジストリの変更は情報システム部門の管轄として扱う——が前提です。適用による設備の停止・データ欠損・機会損失について、筆者・GenbaPy は責任を負いません。

Python は Python Software Foundation(PSF)の商標です。本記事は PSF およびその関連団体とは無関係の第三者による解説です。その他の製品名・会社名(Microsoft・Windows・Smart App Control・PyInstaller・Git など)は、各社または各プロジェクトの商標または登録商標です(利用規約の商標に関する条項)。本サイトの AI 利用方針はサイトについてに記載しています。

あなたの環境で本記事と違う結果が出たら、お問い合わせから教えてください。数値の誤りの指摘も歓迎します。

関連記事

参考文献・一次情報

いずれも 2026-10-09 に参照しました。