Skip to main content
Reverse Engineering

Fix "Bad Magic Number" in a Python `.pyc` File

By Mithun··6 min read
Quick Answer

A `.pyc`'s first bytes are a version-specific *magic number*, so `bad magic number` means the file was compiled by a *different* Python than the one loading it — not that it's corrupt or protected. Read the first two bytes as a little-endian integer and map it to a version: I tested every release, e.g. a7 0d 0d 0a = 3495 = Python 3.11, 2b 0e 0d 0a = 3627 = Python 3.14 (full table below). To fix it, run the file with the matching interpreter or recompile from the .py source. If you only have the .pyc and not the source, recover it with our in-browser .pyc Decompiler — it detects the version for you.

You try to import or run a compiled .pyc — one you extracted from an app, copied off another machine, or found in a __pycache__ folder — and Python refuses it with ImportError: bad magic number. It looks scary, but the cause is almost always mundane: the .pyc was built by a different Python version than the one you're running. Here's exactly what the error means, a tested magic-number table for Python 3.8–3.14 so you can identify the version a .pyc needs, and how to fix every common cause.

The error, and what it actually means

Every .pyc starts with a 4-byte magic number that identifies the exact CPython version that compiled it. When the interpreter loads a .pyc, it checks those bytes first; if they don't match its own, it stops immediately rather than run bytecode it might not understand. Loading a Python 3.11 .pyc under Python 3.14, for example, gives:

ImportError: bad magic number in 'h': b'\xa7\r\r\n'
Loading a 3.11 .pyc under Python 3.14 — real output

Those four bytes — \xa7\r\r\n, i.e. a7 0d 0d 0a — are the give-away: the leading a7 is the 3.11 magic, and 0d 0a is a constant \r\n trailer every version shares. The file is fine; it just needs a 3.11 interpreter. bad magic number never means the .pyc is corrupt or somehow locked — only that the *version* doesn't match.

The magic number bumps on almost every CPython feature release (and sometimes mid-development), because each version can add or change bytecode opcodes. That's by design — it stops an interpreter from silently mis-executing another version's bytecode.

Python `.pyc` magic numbers, 3.8–3.14 (tested)

I read importlib.util.MAGIC_NUMBER from each real interpreter (via the official python:<ver>-slim images) so this table is measured, not copied. The magic is a little-endian 2-byte integer followed by the constant 0d 0a, which together make the 4 header bytes:

PythonMagic numberFirst 4 bytes (hex)
3.8341355 0d 0d 0a
3.9342561 0d 0d 0a
3.1034396f 0d 0d 0a
3.113495a7 0d 0d 0a
3.123531cb 0d 0d 0a
3.133571f3 0d 0d 0a
3.1436272b 0e 0d 0a

Patch releases within a feature version share the same magic — 3.11.0 and 3.11.9 are both 3495 — so any 3.11.x interpreter runs a 3.11 .pyc. You only need to match the major.minor version, not the exact patch.

Identify which Python a `.pyc` needs

You don't need any tools to read the version — the first two bytes are enough. Open the file in binary mode, take the little-endian integer, and look it up:

import sys
MAGIC = {3413: "3.8", 3425: "3.9", 3439: "3.10", 3495: "3.11",
3531: "3.12", 3571: "3.13", 3627: "3.14"}
with open(sys.argv[1], "rb") as f:
head = f.read(4)
n = int.from_bytes(head[:2], "little")
print("first 4 bytes :", head.hex(" "))
print("magic int :", n)
print("compiled by : Python", MAGIC.get(n, "unknown/other"))
identify.py — read the magic number and map it to a version
first 4 bytes : a7 0d 0d 0a
magic int : 3495
compiled by : Python 3.11
Real output on an unknown .pyc

Prefer not to touch the shell? Drop the .pyc into our free .pyc Decompiler — it reads the magic number for you and shows a Python 3.11 · clean recovery badge (and does the same for 3.8 through 3.14), then decompiles the file in the same step.

The usual causes — and the fix for each

  1. Version mismatch (by far the most common). The .pyc was built on a different Python than you're running. Identify the version from the table above, then run the file with a matching interpreter — pyenv install 3.11 / uv python install 3.11, or a python:3.11 Docker image. If you have the original .py, the simplest fix is to just delete the stale .pyc (or the whole __pycache__/) and let Python recompile it for your version.
  2. A moved or rebuilt environment. Copying __pycache__ between machines, or changing a Docker base image (e.g. python:3.11python:3.12), leaves .pyc files from the old version. Clear __pycache__ and reinstall/rebuild so everything is recompiled locally.
  3. A `.pyc` extracted from a PyInstaller app. pyinstxtractor gives you .pyc files that need the *same* Python the app was frozen with, and older PyInstaller versions strip the header so tools can't even read the magic. If that's your case, see decompile a PyInstaller EXE for rebuilding a valid header.
  4. A truncated or corrupted file. If the header is a version you *do* have but you still get an error, the body may be damaged. A short or garbled code object surfaces as a marshal error rather than a magic error:
EOFError: marshal data too short
A .pyc with a valid header but a truncated body — real output

Variants like bad marshal data (unknown type code) or bad marshal data (invalid reference) mean the same thing: the header matched, but the serialized code object after it is incomplete or mangled. Re-extract or re-download the file; there's nothing to "fix" in a byte-damaged .pyc.

You have the `.pyc` but not the source

If you can't get the matching interpreter and you don't have the original .py, the practical move is to recover the source from the .pyc itself. Our in-browser .pyc Decompiler runs pycdc (Decompyle++) via WebAssembly and returns readable Python for 3.0 through 3.12 (partial for 3.13) — with no install and the version detected for you. For 3.13/3.14 bytecode that full decompilers don't rebuild yet, the standard library's marshal + dis still read the strings and logic on any version — the tested method is in can you decompile a Python 3.14 .pyc.

A recovered .pyc gives back variable names, string literals and full logic — which is exactly why a .pyc is a cache format, not a protection layer. If you're shipping code, obfuscate the source before compiling so the recovered result is unreadable.

Identify and decompile a .pyc — free, in your browser

Drop in a .pyc and our decompiler reads its Python version and returns readable source (pycdc via WebAssembly, 3.0–3.12).

Open the .pyc Decompiler

Free tools mentioned here

Related guides

Frequently asked questions

What does 'bad magic number' mean in a .pyc file?

It means the .pyc was compiled by a different Python version than the one trying to load it. The first four bytes of every .pyc are a version-specific magic number; if they don't match the running interpreter, Python refuses the file with 'bad magic number' rather than risk running incompatible bytecode. It does not mean the file is corrupt or protected — just that the version differs.

How do I find out which Python version compiled a .pyc?

Read the first two bytes of the file as a little-endian integer and map it to a version: 3.8=3413, 3.9=3425, 3.10=3439, 3.11=3495, 3.12=3531, 3.13=3571, 3.14=3627. In Python: int.from_bytes(open('x.pyc','rb').read(2), 'little'). Or drop the file into our .pyc Decompiler, which reads the magic number and shows the detected version automatically.

How do I fix a bad magic number error?

Run the .pyc with a matching Python version (identify it from the magic number, then install that version with pyenv/uv or use its Docker image). If you have the original source, the simplest fix is to delete the stale .pyc (or __pycache__) and let Python recompile it. If you only have the .pyc, decompile it back to source.

Why do I get 'bad marshal data' instead of 'bad magic number'?

'bad marshal data' (or 'marshal data too short') means the version header matched, but the serialized code object after the 16-byte header is truncated or corrupted. It's a damaged file, not a version mismatch — re-extract or re-download it. 'bad magic number' is about the version; 'bad marshal data' is about the body.

Does the magic number change with every Python release?

It changes on almost every feature release (3.11→3.12→3.13→3.14 are 3495→3531→3571→3627) because each can add or alter bytecode opcodes. Patch releases within a version share the same magic, so any 3.11.x runs a 3.11 .pyc — you only need to match major.minor.

Keep reading