A few days ago, after one of my regular brainstorming and building sessions with the AI an automation pipeline stopped sending me notifications. That was unusual as it had been sending every day for weeks. But everything else still worked. So, as it is normal, I started the troubleshooting…
The cause was simple and a little embarrassing. During an editing session a few days earlier, one Python file had gotten overwritten with the contents of a completely different file in the same project. The beautiful results of AI thinking that it knows better… And the human trusted the output without validation.
The fix was obvious once I found it: write the missing function back. The harder problem was that I didn’t have the original code anywhere. No version control on this particular file. This is something that should have been running just fine, and to my knowledge, we did not edit this file. So … no backup, or alternative versions, nothing.

That’s where Python’s own caching turned out to be useful in a way it was never designed for.
A quick detour into how Python actually runs your code
Every time Python imports a module, it compiles the source into bytecode and saves that compiled version in a folder called __pycache__, sitting right next to the source file. Next time you import that module, Python checks whether the source changed; if not, it skips compiling and just runs the cached version. It’s purely a speed optimization. Most people never look inside that folder.
But that cache is a snapshot. It was compiled from whatever the source file said at the time, not from whatever it says now. If a file gets overwritten or corrupted after it last ran successfully, the matching cached file can still hold the old, correct version, frozen in bytecode, sitting there until someone runs the file again and triggers a recompile.
That recompile is the catch. The moment you, or your pipeline, executes the broken file even once, Python notices the source changed and overwrites the cache with bytecode compiled from the broken version. The old, good cache is gone for good. In this case, the broken file had already been deployed to a remote server with its own separate cache, but my local copy of the project from the original test had never re-run the broken version, so its cache was still untouched. That was luck, not planning.
Pulling the old code back out
A cached .pyc file is just a small header followed by Python’s internal “code object,” saved with the standard library’s marshal module. Reading it back is two lines:
import marshal
with open(“module.cpython-313.pyc”, “rb”) as f:
f.read(16) # skip the header
code = marshal.load(f)
From there, two tools did the rest. dis.dis(code) disassembles the bytecode into readable instructions, which shows the exact logic: what gets compared to what, what gets concatenated, what order things happen in. Separately, walking code.co_consts recursively surfaces every string literal and docstring buried in the file, which hands back the original log messages and templates verbatim, no instruction-decoding required.
Between the two, I rebuilt the broken files (with the help of AI) almost exactly as they were. What I couldn’t recover were comments and exact formatting; bytecode preserves neither. Everything else came back.
Why this matters beyond one bad afternoon
This is, functionally, the same technique malware analysts use when they find a compiled or obfuscated Python payload and need to know what it actually does. Reading bytecode rather than trusting whatever source is in front of you is a real skill, not just a recovery trick.
It’s also a good reminder of the actual hierarchy of options when you think you’ve lost code:
– Version control, if the file was ever committed. Recovers everything, comments included. Check this first.
– Cloud sync version history (Nextcloud, Dropbox, Drive all keep one if versioning is enabled).
– The __pycache__ trick above, if neither applies and the cache hasn’t been invalidated yet.
– Editor or IDE local history, which several editors keep automatically, and people forget to check.
– Filesystem snapshots, if your system takes them.
None of these replaces just committing your code in the first place. But it’s worth knowing the cache trick exists, because it’s the one option that still works even when everything else goes wrong.