patch-cc 0.5.2__tar.gz → 0.6.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {patch_cc-0.5.2 → patch_cc-0.6.1}/PKG-INFO +13 -12
- {patch_cc-0.5.2 → patch_cc-0.6.1}/README.md +12 -11
- {patch_cc-0.5.2 → patch_cc-0.6.1}/docs/CONDUCT.md +14 -2
- patch_cc-0.6.1/docs/INTERNALS.md +273 -0
- {patch_cc-0.5.2 → patch_cc-0.6.1}/docs/PLAYBOOK.md +43 -14
- {patch_cc-0.5.2 → patch_cc-0.6.1}/docs/corpus.md +72 -29
- patch_cc-0.6.1/scripts/corpus.py +370 -0
- {patch_cc-0.5.2 → patch_cc-0.6.1}/src/patch_cc/__init__.py +1 -1
- patch_cc-0.6.1/src/patch_cc/bun/blob.py +286 -0
- patch_cc-0.6.1/src/patch_cc/bun/container.py +204 -0
- {patch_cc-0.5.2 → patch_cc-0.6.1}/src/patch_cc/bun/elf.py +5 -10
- {patch_cc-0.5.2 → patch_cc-0.6.1}/src/patch_cc/bun/macho.py +4 -9
- {patch_cc-0.5.2 → patch_cc-0.6.1}/src/patch_cc/cli.py +3 -11
- {patch_cc-0.5.2 → patch_cc-0.6.1}/src/patch_cc/doctor.py +3 -4
- {patch_cc-0.5.2 → patch_cc-0.6.1}/src/patch_cc/menu.py +2 -2
- {patch_cc-0.5.2 → patch_cc-0.6.1}/src/patch_cc/patcher.py +9 -2
- {patch_cc-0.5.2 → patch_cc-0.6.1}/src/patch_cc/patches/streaming.py +36 -82
- patch_cc-0.5.2/docs/INTERNALS.md +0 -266
- patch_cc-0.5.2/src/patch_cc/bun/blob.py +0 -544
- patch_cc-0.5.2/src/patch_cc/bun/container.py +0 -276
- {patch_cc-0.5.2 → patch_cc-0.6.1}/.github/workflows/ci.yml +0 -0
- {patch_cc-0.5.2 → patch_cc-0.6.1}/.github/workflows/release-watch.yml +0 -0
- {patch_cc-0.5.2 → patch_cc-0.6.1}/.github/workflows/release.yml +0 -0
- {patch_cc-0.5.2 → patch_cc-0.6.1}/.gitignore +0 -0
- {patch_cc-0.5.2 → patch_cc-0.6.1}/AGENTS.md +0 -0
- {patch_cc-0.5.2 → patch_cc-0.6.1}/CLAUDE.md +0 -0
- {patch_cc-0.5.2 → patch_cc-0.6.1}/LICENSE +0 -0
- {patch_cc-0.5.2 → patch_cc-0.6.1}/docs/demo.gif +0 -0
- {patch_cc-0.5.2 → patch_cc-0.6.1}/pyproject.toml +0 -0
- {patch_cc-0.5.2 → patch_cc-0.6.1}/src/patch_cc/bun/__init__.py +0 -0
- {patch_cc-0.5.2 → patch_cc-0.6.1}/src/patch_cc/bun/errors.py +0 -0
- {patch_cc-0.5.2 → patch_cc-0.6.1}/src/patch_cc/cache.py +0 -0
- {patch_cc-0.5.2 → patch_cc-0.6.1}/src/patch_cc/codex/__init__.py +0 -0
- {patch_cc-0.5.2 → patch_cc-0.6.1}/src/patch_cc/codex/gateway.py +0 -0
- {patch_cc-0.5.2 → patch_cc-0.6.1}/src/patch_cc/codex/models.py +0 -0
- {patch_cc-0.5.2 → patch_cc-0.6.1}/src/patch_cc/codex/oauth.py +0 -0
- {patch_cc-0.5.2 → patch_cc-0.6.1}/src/patch_cc/codex/translate.py +0 -0
- {patch_cc-0.5.2 → patch_cc-0.6.1}/src/patch_cc/js.py +0 -0
- {patch_cc-0.5.2 → patch_cc-0.6.1}/src/patch_cc/locate.py +0 -0
- {patch_cc-0.5.2 → patch_cc-0.6.1}/src/patch_cc/patches/__init__.py +0 -0
- {patch_cc-0.5.2 → patch_cc-0.6.1}/src/patch_cc/patches/agents.py +0 -0
- {patch_cc-0.5.2 → patch_cc-0.6.1}/src/patch_cc/patches/base.py +0 -0
- {patch_cc-0.5.2 → patch_cc-0.6.1}/src/patch_cc/patches/chrome.py +0 -0
- {patch_cc-0.5.2 → patch_cc-0.6.1}/src/patch_cc/patches/codex.py +0 -0
- {patch_cc-0.5.2 → patch_cc-0.6.1}/src/patch_cc/patches/output.py +0 -0
- {patch_cc-0.5.2 → patch_cc-0.6.1}/src/patch_cc/patches/thinking.py +0 -0
- {patch_cc-0.5.2 → patch_cc-0.6.1}/src/patch_cc/ui.py +0 -0
- {patch_cc-0.5.2 → patch_cc-0.6.1}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: patch-cc
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.1
|
|
4
4
|
Summary: Interactive patcher for the Claude Code native binary
|
|
5
5
|
Project-URL: Homepage, https://github.com/anfreire/patch-cc
|
|
6
6
|
Project-URL: Repository, https://github.com/anfreire/patch-cc
|
|
@@ -45,7 +45,7 @@ No Node, no Bun.
|
|
|
45
45
|
uvx patch-cc # fullscreen menu, no install needed
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-

|
|
49
49
|
|
|
50
50
|
## Requirements
|
|
51
51
|
|
|
@@ -195,19 +195,20 @@ uvx patch-cc apply --brand --model Explore=haiku
|
|
|
195
195
|
`uvx patch-cc status` tells you whether the current binary is patched, and the
|
|
196
196
|
startup name / `--version` marker are visible tells too.
|
|
197
197
|
|
|
198
|
-
## Why native-only, and
|
|
198
|
+
## Why native-only, and what the write does
|
|
199
199
|
|
|
200
200
|
Claude Code now ships only as a Bun single-file executable; the npm package is a
|
|
201
201
|
wrapper that downloads it. patch-cc edits the JavaScript modules embedded in the
|
|
202
|
-
binary's `.bun` section
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
202
|
+
binary's `.bun` section — since 2.1.242 the app is code-split across ~1,300 of
|
|
203
|
+
them, and patch-cc treats every one as a single surface. The write never moves a
|
|
204
|
+
byte of the original: each edited module's source is appended and its stale
|
|
205
|
+
precompiled bytecode is unlinked (editing a module's source invalidates its
|
|
206
|
+
bytecode anyway), so a patched binary is a few percent larger than the original,
|
|
207
|
+
and everything else the binary carries — Bun's own records, however its format
|
|
208
|
+
grows — is exactly where it was. That is what keeps the container layer out of
|
|
209
|
+
the way when Bun changes its format under Claude.
|
|
210
|
+
[docs/INTERNALS.md](docs/INTERNALS.md#the-rule-never-move-a-pristine-byte) has
|
|
211
|
+
the reasoning; `patch-cc status` has your numbers.
|
|
211
212
|
|
|
212
213
|
See [docs/INTERNALS.md](docs/INTERNALS.md) for the container format and
|
|
213
214
|
[docs/PLAYBOOK.md](docs/PLAYBOOK.md) for repairing a patch after an update.
|
|
@@ -14,7 +14,7 @@ No Node, no Bun.
|
|
|
14
14
|
uvx patch-cc # fullscreen menu, no install needed
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-

|
|
18
18
|
|
|
19
19
|
## Requirements
|
|
20
20
|
|
|
@@ -164,19 +164,20 @@ uvx patch-cc apply --brand --model Explore=haiku
|
|
|
164
164
|
`uvx patch-cc status` tells you whether the current binary is patched, and the
|
|
165
165
|
startup name / `--version` marker are visible tells too.
|
|
166
166
|
|
|
167
|
-
## Why native-only, and
|
|
167
|
+
## Why native-only, and what the write does
|
|
168
168
|
|
|
169
169
|
Claude Code now ships only as a Bun single-file executable; the npm package is a
|
|
170
170
|
wrapper that downloads it. patch-cc edits the JavaScript modules embedded in the
|
|
171
|
-
binary's `.bun` section
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
171
|
+
binary's `.bun` section — since 2.1.242 the app is code-split across ~1,300 of
|
|
172
|
+
them, and patch-cc treats every one as a single surface. The write never moves a
|
|
173
|
+
byte of the original: each edited module's source is appended and its stale
|
|
174
|
+
precompiled bytecode is unlinked (editing a module's source invalidates its
|
|
175
|
+
bytecode anyway), so a patched binary is a few percent larger than the original,
|
|
176
|
+
and everything else the binary carries — Bun's own records, however its format
|
|
177
|
+
grows — is exactly where it was. That is what keeps the container layer out of
|
|
178
|
+
the way when Bun changes its format under Claude.
|
|
179
|
+
[docs/INTERNALS.md](docs/INTERNALS.md#the-rule-never-move-a-pristine-byte) has
|
|
180
|
+
the reasoning; `patch-cc status` has your numbers.
|
|
180
181
|
|
|
181
182
|
See [docs/INTERNALS.md](docs/INTERNALS.md) for the container format and
|
|
182
183
|
[docs/PLAYBOOK.md](docs/PLAYBOOK.md) for repairing a patch after an update.
|
|
@@ -74,6 +74,18 @@ shape that already absorbs every case, found rather than bolted on.
|
|
|
74
74
|
React idioms before it was cut to three insertions on the tool-use list
|
|
75
75
|
([PLAYBOOK.md](PLAYBOOK.md#live-thinking--streamingpy)).
|
|
76
76
|
|
|
77
|
+
- **Never move a byte you did not write.** The container is the second worked
|
|
78
|
+
case of the rule above. Bun reads a payload through its pointer and ignores
|
|
79
|
+
what nothing points at; a rewrite that re-lays the arena has to re-aim every
|
|
80
|
+
pointer in the blob, including ones in records it has never seen, and Bun
|
|
81
|
+
adds such records whenever it likes — 2.1.246 corrupted one, 2.1.269 was
|
|
82
|
+
refused over one, and the whitelist of known records between the two could
|
|
83
|
+
never be finished. So the write appends what changed and copies everything
|
|
84
|
+
else where it was, and needs to know only what it touches
|
|
85
|
+
([INTERNALS.md](INTERNALS.md#the-rule-never-move-a-pristine-byte)). The price
|
|
86
|
+
is a binary a few percent larger instead of smaller; the size win was the
|
|
87
|
+
claim that bought the fragility.
|
|
88
|
+
|
|
77
89
|
- **Port faithfully.** When you change a patch, verify its output against a real
|
|
78
90
|
bundle — byte-identical where behaviour must not change. `doctor` over the
|
|
79
91
|
archived corpus is that check, and the *diff* between two sweeps is the half
|
|
@@ -84,5 +96,5 @@ shape that already absorbs every case, found rather than bolted on.
|
|
|
84
96
|
- **The user controls commits and releases.** Don't commit, push, or publish
|
|
85
97
|
unless asked.
|
|
86
98
|
|
|
87
|
-
The binary format, and why the
|
|
88
|
-
|
|
99
|
+
The binary format, and why the write appends rather than compacts:
|
|
100
|
+
[INTERNALS.md](INTERNALS.md).
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# Internals
|
|
2
|
+
|
|
3
|
+
How patch-cc gets from a Claude binary to a patched one.
|
|
4
|
+
|
|
5
|
+
## The shape of a native Claude binary
|
|
6
|
+
|
|
7
|
+
Claude Code ships as a [Bun](https://bun.sh) single-file executable. The whole
|
|
8
|
+
app — 34 MB of minified JS on 2.1.269, plus a few asset modules — is embedded
|
|
9
|
+
in the binary:
|
|
10
|
+
|
|
11
|
+
- **Linux**: an ELF section named `.bun`
|
|
12
|
+
- **macOS**: a Mach-O section `__BUN,__bun`
|
|
13
|
+
- **Windows**: a PE `.bun` section (not supported here)
|
|
14
|
+
|
|
15
|
+
Inside that section is a *Bun module graph*: a flat arena of payloads, a module
|
|
16
|
+
table describing them, and a trailer. Before 2.1.242 the graph was the app in one
|
|
17
|
+
module plus a few asset modules; since then the app is **code-split** across
|
|
18
|
+
~1,300 `chunk-*.js` modules that the entry lazily imports (see
|
|
19
|
+
[the split](#the-21242-split-and-the-patchable-surface)). Either way patch-cc
|
|
20
|
+
treats every module the container declares to be JavaScript as one surface.
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
.bun section
|
|
24
|
+
└── [u64 size prefix]
|
|
25
|
+
└── Bun blob
|
|
26
|
+
├── payload arena name / contents / sourcemap / bytecode / ... bytes,
|
|
27
|
+
│ and whatever else the builder placed there
|
|
28
|
+
├── module table N records × 52 bytes
|
|
29
|
+
├── records flag-gated, see below
|
|
30
|
+
├── compileExecArgv
|
|
31
|
+
├── offsets struct 32 bytes: byteCount, modulesPtr, entryId, argvPtr, flags
|
|
32
|
+
└── "\n---- Bun! ----\n" 15-byte trailer
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Every pointer is a `(u32 offset, u32 length)` pair relative to the blob start,
|
|
36
|
+
and Bun reads a payload *through* its pointer — bytes nothing points at are
|
|
37
|
+
ignored. A module record is six such pairs — `name`, `contents`, `sourcemap`,
|
|
38
|
+
`bytecode`, `moduleInfo`, `bytecodeOriginPath` — followed by four `u8` flags
|
|
39
|
+
(`encoding`, `loader`, `moduleFormat`, `side`).
|
|
40
|
+
|
|
41
|
+
Code: `src/patch_cc/bun/blob.py`.
|
|
42
|
+
|
|
43
|
+
## The rule: never move a pristine byte
|
|
44
|
+
|
|
45
|
+
The loader's contract above is the only thing the rewrite rides. How the
|
|
46
|
+
builder lays the arena out, and which records follow the table, is the half of
|
|
47
|
+
the format that changes with Bun — and a rewrite that re-laid the arena would
|
|
48
|
+
have to find and re-aim every pointer in the blob, including pointers in records
|
|
49
|
+
it had never seen. Bun owes that to nobody, and it is where this tool broke
|
|
50
|
+
twice: 2.1.246 (Bun 1.4.1) chained records after the table, and a chain-blind
|
|
51
|
+
compaction shipped a binary that segfaulted in Bun's graph loader while every
|
|
52
|
+
module compared equal; 2.1.269 (Bun 1.4.3) chained two more, and the chain
|
|
53
|
+
walker written after 2.1.246 — mirroring Bun's reader record for record, with a
|
|
54
|
+
whitelist of the bits it knew — refused the build outright. Mirroring the
|
|
55
|
+
reader more completely is a repair that can never be finished.
|
|
56
|
+
|
|
57
|
+
So `rewrite` moves nothing. An edited module's text is appended after the
|
|
58
|
+
arena (NUL-terminated, as Bun's own `count_z` payloads are); the module table
|
|
59
|
+
and everything after it are copied verbatim, shifted by a multiple of 128 so
|
|
60
|
+
every phase inside them holds; then the pointers that name what changed are
|
|
61
|
+
re-aimed:
|
|
62
|
+
|
|
63
|
+
- the edited module's `contents` pair, at its text;
|
|
64
|
+
- its `bytecode` pair, at nothing ([below](#the-bytecode-and-why-we-unlink-it));
|
|
65
|
+
- its source-hash word, to 0 — upstream's own "none, compute it" — because the
|
|
66
|
+
hash keys the text in JSC and the pristine text's hash must not describe ours;
|
|
67
|
+
- the offsets struct's `modulesPtr` and `argvPtr`, along with the bytes they
|
|
68
|
+
name.
|
|
69
|
+
|
|
70
|
+
Every other byte, and every other pointer, is exactly where it was. A record
|
|
71
|
+
patch-cc has never heard of comes through the same copy and still points where
|
|
72
|
+
it did, because what it points at did not move; the arena's payloads keep their
|
|
73
|
+
128-byte phase — Bun deserializes bytecode in place and calls misalignment "a
|
|
74
|
+
runtime assertion error or segfault" — because the arena is not shifted at all.
|
|
75
|
+
|
|
76
|
+
What has to be *known* is exactly what is touched: the offsets struct, two
|
|
77
|
+
pairs and the loader byte of a module record, and the one record that
|
|
78
|
+
describes a module's text — its hash, first after the table when bit 5 of
|
|
79
|
+
`flags` says there is one. The rest of `flags` travels verbatim, bits this code
|
|
80
|
+
has never seen included, with one exception. Bit 4 declares every module's
|
|
81
|
+
text to lie in one contiguous run: Bun's runtime hints the kernel to drop those
|
|
82
|
+
pages after startup, and its comment forbids any other region inside the run.
|
|
83
|
+
The appended text is outside it, so the bit is cleared — Bun documents the
|
|
84
|
+
absent flag as an older layout it reads — rather than left asserting something
|
|
85
|
+
false.
|
|
86
|
+
|
|
87
|
+
## The records after the table
|
|
88
|
+
|
|
89
|
+
Bun ≥ 1.4.1 chains optional records directly after the module table, each
|
|
90
|
+
announced by a `flags` bit and read back in flag order. What has been seen so
|
|
91
|
+
far, for orientation — patch-cc parses none of it beyond the first:
|
|
92
|
+
|
|
93
|
+
| bit | record | since |
|
|
94
|
+
|---|---|---|
|
|
95
|
+
| 5 | `[u32; modules]` — each module's WTF hash of its source text (0 = none) | 2.1.246 |
|
|
96
|
+
| 6 | `u32 count`, then `count` × `{u32 id, ptr}` — internal-module bytecode | 2.1.246 |
|
|
97
|
+
| 7 | one pointer: the shared bytecode string table every chunk's bytecode indexes | 2.1.246 |
|
|
98
|
+
| 8 | `u32` — how many leading modules load before the first `import()` | 2.1.248 |
|
|
99
|
+
| 9 | one pointer: the string table `moduleInfo` bodies index | 2.1.248 |
|
|
100
|
+
| 11 | one pointer to a pre-linked ES module graph, then `u32 count` and `count` × `u32` file index | 2.1.269 |
|
|
101
|
+
| 12 | `u32 flags, u32 value` — runtime options (a JIT policy) | 2.1.269 |
|
|
102
|
+
|
|
103
|
+
Three shapes across the 17 published builds from 2.1.246 to 2.1.269, and none
|
|
104
|
+
of them a code change here: each is bytes in the tail and payloads in the
|
|
105
|
+
arena, and both are copied where they are. The one record whose *meaning* the
|
|
106
|
+
rewrite leans on is bit 11's: a module in the pre-linked graph ships no
|
|
107
|
+
`moduleInfo` body, and its imports and exports are read from the graph rather
|
|
108
|
+
than its text — which holds for an edited module too, because a patch never
|
|
109
|
+
touches module linkage (the syntax gate reads past it,
|
|
110
|
+
[PLAYBOOK.md](PLAYBOOK.md#the-many-module-surface)).
|
|
111
|
+
|
|
112
|
+
## The 2.1.242 split, and the patchable surface
|
|
113
|
+
|
|
114
|
+
Through 2.1.241 the entrypoint module *was* the app: one ~28 MB `contents`
|
|
115
|
+
carrying every line patch-cc anchors on. 2.1.242 turned on Bun code-splitting
|
|
116
|
+
with lazy loading, and the shape changed under the tool:
|
|
117
|
+
|
|
118
|
+
| build | modules | entrypoint `contents` |
|
|
119
|
+
|---|---|---|
|
|
120
|
+
| 2.1.241 | 11 | 28,249,679 bytes (the whole app) |
|
|
121
|
+
| 2.1.243 | 1,385 | 19,952 bytes (an argv shim) |
|
|
122
|
+
|
|
123
|
+
The entrypoint is now a ~20 KB shim that parses argv and lazily
|
|
124
|
+
`import()`s the app across ~1,300 `/$bunfs/root/chunk-*.js` modules; ~46 MB of
|
|
125
|
+
JS, the largest chunk 7.3 MB. The code did not disappear — every anchor is still
|
|
126
|
+
in the binary — but it left the one module patch-cc used to read, and it does not
|
|
127
|
+
concentrate in a single chunk (`branding` spans a dozen modules, `org-label` ten,
|
|
128
|
+
`spinner-tips` six).
|
|
129
|
+
|
|
130
|
+
So the patchable surface is **every module the container declares to be
|
|
131
|
+
JavaScript**, discovered the way the entrypoint itself is discovered — off the
|
|
132
|
+
artifact, never hardcoded. A module's *loader* (the second trailing flag) is how
|
|
133
|
+
Bun decides whether to compile it as source or hand it over as opaque bytes, and
|
|
134
|
+
the entrypoint is by definition the JS Bun runs, so its loader *is* the JS loader
|
|
135
|
+
(`Blob.js_modules`). The asset modules (the native addons, the bundled
|
|
136
|
+
`mermaid`/`hljs`, the HTML template) carry other loaders and are left alone. A
|
|
137
|
+
pre-split build is the one-module case of this — `js_modules()` returns just the
|
|
138
|
+
monolith — so [`js.Source`](PLAYBOOK.md#the-many-module-surface) spans one module
|
|
139
|
+
or a thousand through the same code.
|
|
140
|
+
|
|
141
|
+
The entrypoint still matters for one thing: it is where the manifest lives and
|
|
142
|
+
what `status` reads, named by the offsets struct's `entry_point_id` — the same
|
|
143
|
+
index Bun resolves it by. Its *name* is upstream's to change and we never read
|
|
144
|
+
it: 2.1.229 renamed it `/$bunfs/root/src/entrypoints/cli.js` → `/$bunfs/root/cli`.
|
|
145
|
+
|
|
146
|
+
## The bytecode, and why we unlink it
|
|
147
|
+
|
|
148
|
+
Modules carry precompiled Bun **bytecode**. Before the split only the entry
|
|
149
|
+
module had any (~half of the binary); the code-split builds carry it on nearly
|
|
150
|
+
every chunk (78 MB of 219 on 2.1.268, across ~1,650 modules), and the records
|
|
151
|
+
after the table name another 14 MB — the internal modules' bytecode and the
|
|
152
|
+
shared string table — that no edit touches.
|
|
153
|
+
|
|
154
|
+
Bun runs a module's bytecode in preference to its source, so an edited module's
|
|
155
|
+
bytecode would run the *unpatched* code. `rewrite` therefore aims the edited
|
|
156
|
+
modules' `bytecode` pair at nothing, and Bun compiles those modules from source
|
|
157
|
+
at launch; every untouched module keeps its bytecode and its fast start. The
|
|
158
|
+
stale bytes themselves stay in the file, unreferenced: reclaiming them would
|
|
159
|
+
mean moving everything after them, which is the compaction the rule above
|
|
160
|
+
forbids. A patched binary is therefore a few percent *larger* than the
|
|
161
|
+
original — the edited modules' text, appended — never smaller.
|
|
162
|
+
|
|
163
|
+
Measured on 2.1.268, the default patch set:
|
|
164
|
+
|
|
165
|
+
| binary | size | bytecode the table names |
|
|
166
|
+
|---|---|---|
|
|
167
|
+
| pristine | 219 MB | 78 MB, every module |
|
|
168
|
+
| patched (11 modules edited) | 228 MB | 51 MB, the untouched modules |
|
|
169
|
+
|
|
170
|
+
Read the current figures off any binary with `patch-cc status` rather than off
|
|
171
|
+
this table. Startup does not move: an edited module recompiles from source
|
|
172
|
+
whether its bytecode is dropped or merely unlinked, and the recompile is per
|
|
173
|
+
lazily-imported edited module rather than the whole app at once.
|
|
174
|
+
|
|
175
|
+
Every write asserts each **edited** module names no bytecode in the binary it
|
|
176
|
+
produced (`container.verify`), and `doctor`'s smoke bake writes a temp binary
|
|
177
|
+
through the same `container.write` and *executes* it, so the sweep exercises
|
|
178
|
+
the assert, and the loader itself, on every corpus build. If a future Bun build
|
|
179
|
+
makes bytecode authoritative over source, that assert is the tripwire — every
|
|
180
|
+
edit would silently no-op otherwise.
|
|
181
|
+
|
|
182
|
+
## Writing it back
|
|
183
|
+
|
|
184
|
+
`.bun` is the last *allocated* ELF section; only non-allocated metadata
|
|
185
|
+
(`.comment`, `.symtab`, `.strtab`, `.shstrtab`) follows it. patch-cc rewrites
|
|
186
|
+
the ELF bytes in place:
|
|
187
|
+
|
|
188
|
+
1. Splice the new, larger blob over the old `.bun` bytes.
|
|
189
|
+
2. Shift `e_shoff`, `e_phoff`, and the trailing non-alloc sections/segments by
|
|
190
|
+
the size delta, rounded up to the strictest alignment among them.
|
|
191
|
+
3. Grow the containing `PT_LOAD` segment's `filesz`/`memsz` to match.
|
|
192
|
+
|
|
193
|
+
`.bun` keeps its original file offset. This is deliberately *not* done with a
|
|
194
|
+
general ELF library: LIEF rebuilds the binary and relocates `.bun` so its file
|
|
195
|
+
offset equals its virtual address (`0x20000000`), which inflates the file to
|
|
196
|
+
~715 MB. Raw in-place surgery avoids that entirely.
|
|
197
|
+
|
|
198
|
+
Guards refuse anything that could corrupt the mapping: allocated sections after
|
|
199
|
+
`.bun`, growth into a header table, an unrelated spanning segment, or a
|
|
200
|
+
misaligned `PT_LOAD` shift. If any fires, the write aborts rather than guesses.
|
|
201
|
+
|
|
202
|
+
Code: `src/patch_cc/bun/elf.py`. macOS uses LIEF (`macho.py`): growing a
|
|
203
|
+
Mach-O segment is page-aligned and bounded, with no relocation pathology, and
|
|
204
|
+
growth is the only thing the rewrite ever asks of a container, so the two
|
|
205
|
+
platforms behave the same way. Every edit is followed by an ad-hoc `codesign`
|
|
206
|
+
(mandatory on Apple Silicon).
|
|
207
|
+
|
|
208
|
+
## The manifest
|
|
209
|
+
|
|
210
|
+
Every patched bundle carries a single comment line — appended to the **entry
|
|
211
|
+
module**, the one module always present and always re-extracted, and the one
|
|
212
|
+
`status` reads — describing its shape; [PLAYBOOK.md](PLAYBOOK.md) covers what it
|
|
213
|
+
means for matcher health:
|
|
214
|
+
|
|
215
|
+
```
|
|
216
|
+
//patch-cc {"v":1,"tool":"<version>","patches":[...],"brand":...,"suffix":...,
|
|
217
|
+
"models":{...},"org":...,"codex":{"port":8817,"models":["gpt-5.6-sol"]}}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Every key after `patches` is a configurable patch's own, declared in one place
|
|
221
|
+
(`Patch.setting`) so the manifest here, the cache, and the menu cannot spell it
|
|
222
|
+
three ways. Each is written only when *that* patch landed **and** has a value
|
|
223
|
+
worth recording, so `status` can never assert a name, marker, or model the
|
|
224
|
+
bundle does not contain — and so this is the *widest* the line gets, not its
|
|
225
|
+
fixed shape.
|
|
226
|
+
|
|
227
|
+
That line is why `patch-cc status` can name exactly what is applied: several
|
|
228
|
+
patches are value flips (`verbose:!0`) that leave no other trace. A comment
|
|
229
|
+
can't collide with code and travels with the bundle through extract/repack.
|
|
230
|
+
The menu also reads it to pre-select the current patch set — the binary is the
|
|
231
|
+
state.
|
|
232
|
+
|
|
233
|
+
Each key records what was *asked for*, never what was derived from it. `codex`
|
|
234
|
+
carries model ids and a port and nothing else: a Codex model's display name and
|
|
235
|
+
context window are already baked into the bundle, and repeating them here would
|
|
236
|
+
be a second copy — one that a relabelling upstream could make disagree with the
|
|
237
|
+
binary it claims to describe. That is also what makes the manifest the single
|
|
238
|
+
home for the gateway port: `codex serve` and `codex status` read it from here
|
|
239
|
+
rather than from a store of their own.
|
|
240
|
+
|
|
241
|
+
## Safety
|
|
242
|
+
|
|
243
|
+
- Before the first patch of a version, the pristine binary is copied to
|
|
244
|
+
`~/.local/share/patch-cc/backups/`. `restore` copies it back — never an
|
|
245
|
+
inverse patch (insertions cascade, so a reverse diff is meaningless).
|
|
246
|
+
- Patching always starts from that pristine copy, so re-applying never stacks
|
|
247
|
+
edits on edits, and an apply where **nothing lands** — every selected patch
|
|
248
|
+
broken, so the manifest would claim nothing — leaves the binary untouched
|
|
249
|
+
entirely (unlinking bytecode for nothing would only slow startup). A patch
|
|
250
|
+
that *lands* still writes even where it changed no bytes, because landing
|
|
251
|
+
includes an override the build already satisfies: the manifest records what
|
|
252
|
+
was asked and verified present, so `status` can report it.
|
|
253
|
+
- The bundle is parsed, and any syntax error aborts before the binary or the
|
|
254
|
+
backup is touched — the two checks below answer "did we write what we meant
|
|
255
|
+
to", which a corrupt splice satisfies perfectly. The parse is the same one
|
|
256
|
+
the patches locate with, so it costs nothing extra and sits at *every* batch
|
|
257
|
+
of edits as well as on the final bytes: a patch that produces rubble is named
|
|
258
|
+
and dropped rather than aborting the run. See
|
|
259
|
+
[PLAYBOOK.md](PLAYBOOK.md#the-syntax-gate).
|
|
260
|
+
- Every write is verified: patch-cc re-extracts the JS from the binary it just
|
|
261
|
+
wrote and asserts every module equals what it meant to write, and that the
|
|
262
|
+
blob is the pristine blob plus exactly the intended edits — the arena
|
|
263
|
+
byte-identical in place; the module table and the tail verbatim one shift
|
|
264
|
+
along, except each edited module's two pairs and hash word; the flags less
|
|
265
|
+
the contiguity bit; the same entry module and the same `compileExecArgv`.
|
|
266
|
+
Nothing is enumerated, so a record this code has never parsed is covered by
|
|
267
|
+
the same comparison as the ones it has. The check exists because 2.1.246
|
|
268
|
+
failed *only* there: every module compared equal while the written binary
|
|
269
|
+
was dead.
|
|
270
|
+
- Patching a binary that is already marked, when no pristine backup exists, is
|
|
271
|
+
refused outright — there is nothing clean to start from, and our edits change
|
|
272
|
+
lengths, so a second pass would corrupt rather than update. `restore` or a
|
|
273
|
+
reinstall are the only honest fixes; there is deliberately no override.
|
|
@@ -123,6 +123,20 @@ The tree removes the *mechanical* fragility. It does not decide these:
|
|
|
123
123
|
the bundle one identity, the point where that path is fed, where a twin of
|
|
124
124
|
the path owes one per step, each a claim only the patch needs.
|
|
125
125
|
`live-thinking` is the worked case ([below](#live-thinking--streamingpy)).
|
|
126
|
+
- **Anchor on the fact, not on a consumer of it.** When upstream keeps a fact
|
|
127
|
+
under a name of its own — a setting, a header, a store field — and every
|
|
128
|
+
site that acts on it sits downstream of one read, default or flip *that
|
|
129
|
+
read* and leave the sites alone: the sites are where upstream respells
|
|
130
|
+
things, the name is what it keeps. `display-mode` paid for the lesson on
|
|
131
|
+
2.1.277. It defaulted the request's thinking display at the site that chose
|
|
132
|
+
it, reached through the env-var read that shared the value's `let`
|
|
133
|
+
(comma-fusion, the minifier's) and identified as the consequence of a
|
|
134
|
+
ternary whose alternative was `void 0` (the branch layout, upstream's); the
|
|
135
|
+
build flipped the branches and grew a `"highlights"` mode inside the value,
|
|
136
|
+
and the step found nothing with every anchor standing — while the fact,
|
|
137
|
+
`showThinkingSummaries`, was one member read in one spelling on every build
|
|
138
|
+
in the corpus. Where a value *must* be identified at its site, ask what it
|
|
139
|
+
can *be* (`js.values`), never which branch it sits on.
|
|
126
140
|
- **Match an identity by the weakest claim that still proves it; hold a rewrite
|
|
127
141
|
to the exact node.** The two jobs pull in opposite directions.
|
|
128
142
|
`thinking-inline`'s null-guard neutralises only a `return` that answers
|
|
@@ -215,7 +229,7 @@ Anything the binary can enumerate for us, it does:
|
|
|
215
229
|
three entries when 2.1.229 renamed the module out from under it
|
|
216
230
|
(`…/src/entrypoints/cli.js` → `…/cli`) and the tool stopped opening binaries
|
|
217
231
|
at all — with the answer already parsed, already carried on the dataclass, and
|
|
218
|
-
already written back unchanged by `
|
|
232
|
+
already written back unchanged by `rewrite`. A list that only ever grows is
|
|
219
233
|
answering the wrong question. An id past the end of the module table is a
|
|
220
234
|
corrupt blob and raises; there is deliberately no fallback to guessing by
|
|
221
235
|
name, because a container that cannot say which module it runs is not one to
|
|
@@ -505,8 +519,9 @@ Most patches are one rewrite. `live-thinking` is five named sub-steps, because
|
|
|
505
519
|
a single hit count cannot tell "all landed" from "half silently drifted", and
|
|
506
520
|
each records its own `candidates`/`applied`:
|
|
507
521
|
|
|
508
|
-
- **`display-mode`** — the
|
|
509
|
-
|
|
522
|
+
- **`display-mode`** — the `showThinkingSummaries` setting defaults on, so the
|
|
523
|
+
request asks the API for summarised thinking text; without it the stream
|
|
524
|
+
carries signatures and no words.
|
|
510
525
|
- **`reducer`** — the stream reducer is found together with the options bag it
|
|
511
526
|
is handed the live tool-use setter in, and our binding of that setter is
|
|
512
527
|
added to the bag.
|
|
@@ -602,7 +617,9 @@ that broke a patch along with every note.
|
|
|
602
617
|
|
|
603
618
|
6. Sweep the fix over the versions you still have. `doctor` takes a path, and
|
|
604
619
|
every binary patch-cc has ever touched left a pristine copy in
|
|
605
|
-
`~/.local/share/patch-cc/backups
|
|
620
|
+
`~/.local/share/patch-cc/backups/` — which is only the builds *this machine*
|
|
621
|
+
patched, so `uv run scripts/corpus.py sync` first to make that the published
|
|
622
|
+
set ([corpus.md](corpus.md)):
|
|
606
623
|
|
|
607
624
|
```bash
|
|
608
625
|
sweep() { for b in ~/.local/share/patch-cc/backups/*.orig; do
|
|
@@ -879,15 +896,24 @@ for you. Each entry: what it changes, the stable anchor, and where it lives.
|
|
|
879
896
|
drew beside the landed block for as long as the answer streamed. A
|
|
880
897
|
tool-use entry at the same index is left exactly as the twin leaves it,
|
|
881
898
|
and a list with nothing to drop is returned as it was.
|
|
882
|
-
- **`display-mode`** defaults the
|
|
883
|
-
`"summarized"
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
899
|
+
- **`display-mode`** defaults the `showThinkingSummaries` setting on. Upstream
|
|
900
|
+
asks the API for `"summarized"` exactly when that setting is on, and routes
|
|
901
|
+
every consequence — the request's display, the beta header it withholds
|
|
902
|
+
while summaries are on, the `updates` display the request otherwise falls
|
|
903
|
+
back to — off one read of it, so that read
|
|
904
|
+
gains a default (`(settings().showThinkingSummaries??!0)`) and the request
|
|
905
|
+
is never touched. Every member read of the name, as `spinner-tips` reads
|
|
906
|
+
its setting, and a write is skipped for the reason it gives; the read is
|
|
907
|
+
one on every build in the corpus, spelled `settings().name??!1` on all of
|
|
908
|
+
them, and none of that is claimed. An explicit `false` in the user's
|
|
909
|
+
settings still wins. Until 2.1.277 this step defaulted the request's
|
|
910
|
+
display value instead, reached through the `CLAUDE_CODE_DISABLE_THINKING`
|
|
911
|
+
read that shared its `let` and identified as the consequence of a ternary
|
|
912
|
+
whose alternative was `void 0`; that build flipped the branches
|
|
913
|
+
(`!x?void 0:…`) and grew a `"highlights"` mode inside the chosen value,
|
|
914
|
+
and the step read *found nothing* with every anchor standing. Both claims
|
|
915
|
+
were syntax between the anchors ([What still takes
|
|
916
|
+
judgement](#what-still-takes-judgement)).
|
|
891
917
|
|
|
892
918
|
**What is deliberately not done.** No state of ours: upstream's store carries
|
|
893
919
|
a `streamingThinking` slot with a setter and a thirty-second linger, written
|
|
@@ -934,7 +960,10 @@ for you. Each entry: what it changes, the stable anchor, and where it lives.
|
|
|
934
960
|
slots (2.1.257 — not the compiler arriving, which had cached the
|
|
935
961
|
conversation render since 2.1.247, but this component joining it). The
|
|
936
962
|
reducer half, anchored on the API's event strings, has not moved since it
|
|
937
|
-
became an insertion at dispatch points.
|
|
963
|
+
became an insertion at dispatch points. The display default broke once
|
|
964
|
+
(2.1.277), on the branch layout of the ternary that chose the request's
|
|
965
|
+
value, and moved off the request onto the setting upstream chooses it from.
|
|
966
|
+
Prefer claims upstream cannot drop
|
|
938
967
|
without paying for them — a dispatch string, a store's field name, the twin's
|
|
939
968
|
own list — over claims only this patch needs, which is what CONDUCT's *ride
|
|
940
969
|
what upstream ships working* asks. What was cut, and why each piece was a
|
|
@@ -10,26 +10,32 @@ claim is one command from re-verification instead of a number you have to trust.
|
|
|
10
10
|
Before the first patch of a version, patch-cc copies the pristine binary to
|
|
11
11
|
`~/.local/share/patch-cc/backups/<version>.orig` ([INTERNALS.md](INTERNALS.md#safety)).
|
|
12
12
|
The corpus is exactly those copies: not a fixture checked into the repo (each is
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
a few hundred MB), but a directory this machine fills. Left alone it fills
|
|
14
|
+
**unevenly** — a build patched here leaves its `.orig`, a build that shipped while
|
|
15
|
+
this machine sat idle leaves nothing — so the set samples an update cadence rather
|
|
16
|
+
than a release history, and its holes are invisible from inside it.
|
|
17
|
+
`scripts/corpus.py` closes that: Anthropic still serves every build it published,
|
|
18
|
+
so the set is completed off the artifact, like everything else here.
|
|
16
19
|
|
|
17
20
|
The binaries are the artifact, not this file — `doctor` recomputes every count
|
|
18
|
-
from them, so nothing here can drift from what a matcher actually does.
|
|
19
|
-
file
|
|
20
|
-
|
|
21
|
+
from them, so nothing here can drift from what a matcher actually does. Nor is
|
|
22
|
+
this file where "pristine" is defined: every release manifest carries a
|
|
23
|
+
per-platform sha256 and each `.orig` is checked against it. The table below is
|
|
24
|
+
where that check is written down once per build, so its hashes are the offline
|
|
25
|
+
record for builds Anthropic may one day stop serving — never a second authority
|
|
26
|
+
to keep in step.
|
|
21
27
|
|
|
22
28
|
## On disk now
|
|
23
29
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
(
|
|
30
|
-
`2.1.
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
One row per pristine binary held, appended as builds ship and never restated — so
|
|
31
|
+
a hash that stops matching means the file changed under you, not that a number
|
|
32
|
+
moved. A gap in the numbering is upstream's rather than a miss: not every version
|
|
33
|
+
reached the release channel, and `corpus.py status` names which, so no list of
|
|
34
|
+
them is kept here to go stale. The set straddles the **2.1.242 code split**
|
|
35
|
+
(INTERNALS.md): `2.1.242`/`2.1.243` are the first many-module builds, and they
|
|
36
|
+
jump ~35 MB over `2.1.241` for it. Sizes repeat across builds, which is why the
|
|
37
|
+
identity column is the hash (of the whole file, `sha256sum <version>.orig`) and
|
|
38
|
+
never the size:
|
|
33
39
|
|
|
34
40
|
| version | size | sha256 |
|
|
35
41
|
|---|---|---|
|
|
@@ -68,26 +74,63 @@ the whole file, `sha256sum <version>.orig`) and never the size:
|
|
|
68
74
|
| `2.1.243` | 378 MB | `4b0dafeedd0b469c41988e200036fd773e7553ba960349c9f02a82c6d1f2ba27` |
|
|
69
75
|
| `2.1.245` | 392 MB | `16ad2b94deaf7b29abed966d981c9991a47af0420f5be8ed4a3f83bea9f678bc` |
|
|
70
76
|
| `2.1.246` | 248 MB | `1a0a662dc1bb938eaec38545abce9a4a69113d7d7f7c5e1a553ea276617b906a` |
|
|
71
|
-
| `2.1.247` |
|
|
72
|
-
| `2.1.248` |
|
|
73
|
-
| `2.1.250` |
|
|
74
|
-
| `2.1.251` |
|
|
75
|
-
| `2.1.252` |
|
|
76
|
-
| `2.1.257` |
|
|
77
|
+
| `2.1.247` | 250 MB | `5fb321bf417ffc5cd4e3f36e7c9c7e029bf47aaa36d5621db979fcc5e6eabe15` |
|
|
78
|
+
| `2.1.248` | 224 MB | `3edee3cb054bd6823674fd60d5c0e442825b28ee8fbf815af2d16bf0de072e16` |
|
|
79
|
+
| `2.1.250` | 224 MB | `2be252a00ac56e704d7fbf7e5e9ef1243584093334a861945238a0c27e84bdac` |
|
|
80
|
+
| `2.1.251` | 214 MB | `fd5f10ff0eb58daec04900466b143ea98aab50abf208a422bc008eaec13f61f7` |
|
|
81
|
+
| `2.1.252` | 214 MB | `a715a45105e593fc9808d035d77781f88480b9897975a9df41837f0c591bd4b3` |
|
|
82
|
+
| `2.1.257` | 215 MB | `9a64bda9d8722a1fa05bef9a5961d07e0331b99597eda9e2f6a732f3a0ff7f05` |
|
|
83
|
+
| `2.1.258` | 215 MB | `704f1334ac65d3e89e1c6c1d7663293ad786a6166afdb71b5075337df630f976` |
|
|
84
|
+
| `2.1.259` | 217 MB | `f7dd62ae415378018cd21dd950eb3bac174ab085830304d3b8b098146bfd47b6` |
|
|
85
|
+
| `2.1.260` | 215 MB | `7a2fdc74b6836ea3d183f665b869f0ee3baebc9713cbebffe5838da4ea7bd82e` |
|
|
86
|
+
| `2.1.261` | 216 MB | `4ae40dd1784e85753e742e09f267d29ecbb82890361ad3817d27560866d364a6` |
|
|
87
|
+
| `2.1.263` | 216 MB | `26d020351e8112f4006790f3cfce43b4c9df0c1bb1d0e542364d64151b81d5ba` |
|
|
88
|
+
| `2.1.265` | 216 MB | `e14738e3a58d1fc6ccc23b9c919451b4846bc27074a3fb48db976a7d595bdeeb` |
|
|
89
|
+
| `2.1.266` | 216 MB | `19842705e989393fce936804df6d2ab034860e24b8f8880357981d87ffd83fac` |
|
|
90
|
+
| `2.1.267` | 217 MB | `0399c793ff571d5946ef923d80b4f330d05ac4b6842a6b0775468f5d389403c0` |
|
|
91
|
+
| `2.1.268` | 219 MB | `9691a2b7bd796712ca8cffb8e32e54ff7fc45b662540233171a16a94a0425653` |
|
|
92
|
+
| `2.1.269` | 220 MB | `25e44883f54419569a3d739f38cbbdaebe83b09895da0f343e1b003710a4775b` |
|
|
93
|
+
| `2.1.270` | 224 MB | `3a624a5a7cd79bbad4d32bd7db36f1197ecf458bc5bf1e2aed81834a01ad3ef0` |
|
|
94
|
+
| `2.1.271` | 227 MB | `5e7b6fc24d0e124f68e99a0641c47b662945c6d197d768d7ad7a72cbf1897f58` |
|
|
95
|
+
| `2.1.272` | 227 MB | `d81396a668eb76fbddb49a2a5841f1b5d7af96b4c1f6500ced92f2c988f5bcd4` |
|
|
96
|
+
| `2.1.273` | 229 MB | `6c752e2cc7c110c9df15f26d8d134d438c5ae95dbd610efc1a308bf7f9c5f6c1` |
|
|
97
|
+
| `2.1.274` | 231 MB | `15e2d05148f801b5774032faad87e624ecd172e9903288bda448b892eb58fa07` |
|
|
98
|
+
| `2.1.275` | 232 MB | `13586f3150a7ca1655f36e1dba759fb404e0f7cf7021d4a3dcd5e6f604e56156` |
|
|
99
|
+
| `2.1.276` | 232 MB | `8a56c8a14bd3cb246e2bdb7e60aefe0f609bff78c8bbcc5ea6b1817c111c6145` |
|
|
100
|
+
| `2.1.277` | 234 MB | `722210f05ba494d8f6df69423c4d4f2960900f7a007d0532851c7a36e375cab7` |
|
|
101
|
+
| `2.1.278` | 234 MB | `5c4735937844e84f8a93306e841a5b0e12252909b07870f789b190468da147ab` |
|
|
77
102
|
|
|
78
103
|
This set covers the span the playbook's tree-move measurements were taken over
|
|
79
104
|
(`2.1.210` → `2.1.233`), the 2.1.242 split, the 2.1.246 stream-store
|
|
80
|
-
migration, the 2.1.247 selector reads it was read back through,
|
|
105
|
+
migration, the 2.1.247 selector reads it was read back through, the
|
|
81
106
|
2.1.257 compiled transcript renderer that retired live thinking's render half,
|
|
82
|
-
|
|
107
|
+
and the 2.1.269 Bun 1.4.3 records that retired the container's record walker, so
|
|
108
|
+
each is re-checkable here rather than historical.
|
|
83
109
|
|
|
84
|
-
##
|
|
110
|
+
## Keep it complete
|
|
85
111
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
112
|
+
```bash
|
|
113
|
+
uv run scripts/corpus.py # what upstream serves, against what is held
|
|
114
|
+
uv run scripts/corpus.py sync # fetch every published build the set lacks
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`status` is read-only and exits non-zero when the set is short or damaged. It
|
|
118
|
+
keeps five answers apart rather than counting them together ([CONDUCT.md](CONDUCT.md)):
|
|
119
|
+
`ok`, `missing` (served, not held), `unpublished` (never served — a gap, not a
|
|
120
|
+
miss), `unverifiable` (held, no longer served, so nothing left to check it
|
|
121
|
+
against) and `corrupt` (held, and not what upstream shipped). `sync` acts on
|
|
122
|
+
`missing` alone: a `corrupt` entry is evidence that something here damaged a
|
|
123
|
+
binary, and replacing it silently would destroy the only sign of that — delete it
|
|
124
|
+
and re-run to take a fresh copy.
|
|
125
|
+
|
|
126
|
+
The span walked is the oldest build held → the channel's head, so no version
|
|
127
|
+
number is written down anywhere; `--since` moves the floor, and builds already
|
|
128
|
+
held are read, tabled and swept whatever the span says. A machine holding
|
|
129
|
+
nothing has made no statement about how far back it cares, so the walk starts at
|
|
130
|
+
the head — `--since` is how a fresh checkout asks for history. A build this machine
|
|
131
|
+
never patched can still join the old way — save its pristine native binary as
|
|
132
|
+
`~/.local/share/patch-cc/backups/<version>.orig` — and the next run verifies it
|
|
133
|
+
like any other. To read the JS a given binary carries without patching anything:
|
|
91
134
|
|
|
92
135
|
```bash
|
|
93
136
|
patch-cc extract ~/.local/share/patch-cc/backups/2.1.233.orig > 2.1.233.js
|