[agent] Found by the scheduled pip / requirements.txt bug-hunt routine (ledger #309).
Summary
By default pip byte-compiles every installed .py file into __pycache__/*.pyc. Without SOURCE_DATE_EPOCH, those .pyc files are timestamp-validated: CPython reuses the bytecode as long as the source file's mtime in whole seconds and its size match the values in the .pyc header.
Agent-mode apply replaces the source by stage + rename (apply_file_patch_at, crates/socket-patch-core/src/patch/apply.rs:422). It doesn't touch __pycache__. Suppose the patched file is the same size as the original (a flipped constant or operator, or a same-length string), and apply runs within the same wall-clock second as the install, as it does in pip install -r requirements.txt && socket-patch apply for packages extracted near the end of the install. Then the .pyc header still matches, and every later import runs the unpatched bytecode.
Meanwhile:
- the patched source is on disk;
apply reports Patched packages: pkg:pypi/six@1.16.0;
- a re-run says
(already patched), so nothing ever repairs it;
- anything that hash-checks the source (verification,
vex) treats the package as patched.
The stale .pyc stays in place until the source's mtime changes again. The same applies in reverse to rollback of such a patch: the source is restored, but the patched bytecode keeps running.
Impact
The fix silently doesn't take effect at runtime, with no warning. Conditions: pip's default byte-compilation, a same-size patch, and apply within the same second as the install. A size-changing patch in the same second is fine (control below), and so is a same-size patch applied in a later second.
Repro (Linux, real pip install, offline manifest staging)
# Stage .socket/manifest.json + blobs for pkg:pypi/six@1.16.0 whose six.py patch is size-preserving
pip download -q --no-deps six==1.16.0 -d dl
python3 - <<'EOF'
import hashlib,json,os,zipfile
orig=zipfile.ZipFile('dl/six-1.16.0-py2.py3-none-any.whl').read('six.py')
pat=orig.replace(b'Benjamin Peterson <benjamin',b'PATCHEDX Peterson <benjamin',1); assert len(pat)==len(orig)
g=lambda b: hashlib.sha256(b'blob %d\0'%len(b)+b).hexdigest()
os.makedirs('pr/.socket/blobs',exist_ok=True)
json.dump({"patches":{"pkg:pypi/six@1.16.0":{"uuid":"5c3e1a2b-7d4f-4e6a-9b8c-1d2e3f4a5b6c","exportedAt":"2026-01-01T00:00:00Z",
"files":{"six.py":{"beforeHash":g(orig),"afterHash":g(pat)}},"vulnerabilities":{},"description":"x","license":"MIT","tier":"free"}}},
open('pr/.socket/manifest.json','w'))
for b in (orig,pat): open('pr/.socket/blobs/'+g(b),'wb').write(b)
EOF
python3 -m venv pr/.venv
python3 -c 'import time;time.sleep(1-time.time()%1+0.02)' # start of a second
pr/.venv/bin/pip install -q --no-index dl/six-1.16.0-py2.py3-none-any.whl
socket-patch apply --offline --cwd "$PWD/pr"
S=pr/.venv/lib/python3*/site-packages
stat -c '%Y %s %n' $S/six.py $S/__pycache__/six*.pyc # same second
grep -c PATCHEDX $S/six.py # 1: patched on disk
pr/.venv/bin/python -c 'import six; print(six.__author__)' # Benjamin Peterson … ← unpatched at runtime
pr/.venv/bin/python -X pycache_prefix=/tmp/fresh -c 'import six; print(six.__author__)' # PATCHEDX Peterson …
socket-patch apply --offline --cwd "$PWD/pr" # "(already patched)"
Expected vs actual
- Expected: after a successful agent-mode
apply, the installed package runs the patched code. CLI_CONTRACT.md / README describe agent mode as applying the patch in place to the installed files. Python's import system decides what actually runs, so apply (and rollback) should invalidate the matching __pycache__/<stem>.*.pyc, or make sure the new mtime can't collide with the one in the header.
- Actual: the source is patched, but the timestamp-validated
.pyc stays valid, and the unpatched bytecode runs. Exit 0, no warning.
Matrix (Linux, main 045d7ec)
| Python / pip |
same second, same-size patch |
same second, size-changing patch |
later second, same-size patch |
| CPython 3.8 / pip 20.3.4 |
fail (runtime unpatched) |
— |
— |
| CPython 3.11 / pip 26.2.1 |
fail (2 of 2) |
pass |
pass |
| CPython 3.13 / pip 24.0 |
fail |
— |
— |
macOS and Windows weren't probed. This is CPython's timestamp .pyc rule, which is the same on every OS, and NTFS / APFS mtimes behave the same way at one-second granularity. Not affected: SOURCE_DATE_EPOCH set (pip then writes checked-hash .pyc files), pip install --no-compile, uv without --compile-bytecode, and hosted / vendored mode (the wheel is compiled from patched source).
Suspect code
crates/socket-patch-core/src/patch/apply.rs:422 (apply_file_patch_at): it writes the new .py with no __pycache__ invalidation. The rollback write path is the same.
- Possible fixes: after committing a
*.py file in a PyPI package, remove __pycache__/<stem>.*.pyc (and the legacy <stem>.pyc). Or, if the new mtime's second equals the old one and the size is unchanged, bump the mtime.
No probe runs: the behaviour is CPython-defined, and the sandbox can't delete probe branches.
[agent] Found by the scheduled pip / requirements.txt bug-hunt routine (ledger #309).
Summary
By default pip byte-compiles every installed
.pyfile into__pycache__/*.pyc. WithoutSOURCE_DATE_EPOCH, those.pycfiles are timestamp-validated: CPython reuses the bytecode as long as the source file's mtime in whole seconds and its size match the values in the.pycheader.Agent-mode
applyreplaces the source by stage + rename (apply_file_patch_at,crates/socket-patch-core/src/patch/apply.rs:422). It doesn't touch__pycache__. Suppose the patched file is the same size as the original (a flipped constant or operator, or a same-length string), andapplyruns within the same wall-clock second as the install, as it does inpip install -r requirements.txt && socket-patch applyfor packages extracted near the end of the install. Then the.pycheader still matches, and every laterimportruns the unpatched bytecode.Meanwhile:
applyreportsPatched packages: pkg:pypi/six@1.16.0;(already patched), so nothing ever repairs it;vex) treats the package as patched.The stale
.pycstays in place until the source's mtime changes again. The same applies in reverse torollbackof such a patch: the source is restored, but the patched bytecode keeps running.Impact
The fix silently doesn't take effect at runtime, with no warning. Conditions: pip's default byte-compilation, a same-size patch, and
applywithin the same second as the install. A size-changing patch in the same second is fine (control below), and so is a same-size patch applied in a later second.Repro (Linux, real pip install, offline manifest staging)
Expected vs actual
apply, the installed package runs the patched code. CLI_CONTRACT.md / README describe agent mode as applying the patch in place to the installed files. Python's import system decides what actually runs, soapply(androllback) should invalidate the matching__pycache__/<stem>.*.pyc, or make sure the new mtime can't collide with the one in the header..pycstays valid, and the unpatched bytecode runs. Exit 0, no warning.Matrix (Linux, main
045d7ec)macOS and Windows weren't probed. This is CPython's timestamp
.pycrule, which is the same on every OS, and NTFS / APFS mtimes behave the same way at one-second granularity. Not affected:SOURCE_DATE_EPOCHset (pip then writes checked-hash.pycfiles),pip install --no-compile, uv without--compile-bytecode, and hosted / vendored mode (the wheel is compiled from patched source).Suspect code
crates/socket-patch-core/src/patch/apply.rs:422(apply_file_patch_at): it writes the new.pywith no__pycache__invalidation. The rollback write path is the same.*.pyfile in a PyPI package, remove__pycache__/<stem>.*.pyc(and the legacy<stem>.pyc). Or, if the new mtime's second equals the old one and the size is unchanged, bump the mtime.No probe runs: the behaviour is CPython-defined, and the sandbox can't delete probe branches.