Update 2026-07-17: the three builds I skipped — 0.4.17, 0.4.18, 0.4.19 — and a correction about which of them I actually have
Date: 2026-07-17 Author: Jesper (JesperLive / MrSataana), developer of GRIP - Enhanced Macro Sequencer (GRIP-EMS) Subject: GSE Companion v0.4.18 and v0.4.19 (and v0.4.17, which I do not have) Previous update: UPDATE-2026-07-17-v0.4.26.md
Why this exists
The README says, in its opening verification note, that this repository documents v0.4.12 “and the dated updates below track every build since.”
That was not true. Three builds shipped between v0.4.16 and v0.4.20 and no update in this repository ever covered them. Nobody pointed this out. I found it going back over my own claims, and the honest thing is a write-up rather than a quieter sentence in the README.
So: here is what is in the builds I skipped. The answer is boring, and I am publishing it anyway, because “I checked and there was nothing” is only worth something if you say it when it is true.
First, a correction about my own collection
I thought I had three builds. I have two.
The file on my disk named GSE Companion Setup 0.4.17.exe is not build 0.4.17. It is
byte-identical to GSE Companion Setup 0.4.18.exe — same SHA-256, same length — and the
package.json inside its app.asar declares "version": "0.4.18". My file timestamps show the two
downloads 38 seconds apart. I downloaded the same installer twice and the second copy landed under
the wrong name.
Build 0.4.17 was never on my disk. I have no observation of it at all. Its status here is exactly the status of 0.4.25 in the previous update: absent, and I am not going to guess.
I am recording this rather than quietly retitling the file, because I made the error in a specific
way that this repository is supposed to be careful about: I read a filename and asserted about a
build. The filename is not the build. The build says what it is, in package.json, and that was
sitting inside the archive the whole time. It cost me a wrong claim that I had 0.4.17 and that it
was identical to 0.4.18 — the same shape of mistake as reading one artifact and reporting on
another, which is the error I have already had to correct twice in this repository.
Chain of custody
Extracted from the installers with the same two-stage route the other updates use. No code was run.
| Artifact | SHA-256 | Size |
|---|---|---|
| GSE Companion Setup 0.4.18.exe | 978cc99a5f101072542070b0e031fb92247f9b047511acfbb6d9890373e156ef |
81,324,864 B |
| GSE Companion Setup 0.4.17.exe (the same file, misnamed) | 978cc99a5f101072542070b0e031fb92247f9b047511acfbb6d9890373e156ef |
81,324,864 B |
| resources/app.asar (0.4.18) | 2fcf593c3e952944361084e923c630dbe79f60822fa4842db1997eb5469aba39 |
— |
| out/main/index.js (0.4.18) | 0f1a93153a0a98125cd6ad6972f9a040682ef82bb4532f29c32cb6ab328f18a2 |
132,917 B |
| GSE Companion Setup 0.4.19.exe | 743b816e9ad2beecda463a3264fc7623643e628f2ed467f4aa994e155fb72d08 |
81,325,163 B |
| resources/app.asar (0.4.19) | 306c6e9540a6ffde62fa2b49d13c04b155da6c38d594f58c1c77dec93d60af1a |
— |
| out/main/index.js (0.4.19) | d62f1bb0e984c74a69e178c16c0053c28473a4b2519f466a402009f817bc4c14 |
134,040 B |
The extraction is checked, not asserted. Running the same route over the 0.4.16 installer
reproduces 84a72f8636c07f1821cba22ff85aae65c530442ae197bc6149922482c8131b70 for its app.asar and
5271373bd3de213973282172aa339002dc71c2c88fed39efb13249fbcb078cc2 / 128,766 bytes for its
index.js — the hashes and the byte size already published in the 2026-06-21 update. If the method
were wrong, that control would not land.
What is in 0.4.18 and 0.4.19
Marker counts, read out of each build’s out/main/index.js. 0.4.16 and 0.4.20 are here as the
bookends, so you can see the gap in context rather than on its own.
| marker | 0.4.16 | 0.4.18 | 0.4.19 | 0.4.20 |
|---|---|---|---|---|
integrityRef (the server-supplied detection target) |
4 | 4 | 4 | 4 |
restrictedAccount (the account flag) |
5 | 5 | 5 | 5 |
b531cb8b…683a (the embedded ed25519 key) |
1 | 1 | 1 | 1 |
sign.detached.verify (the signature gate) |
1 | 1 | 1 | 1 |
force-run (the unsigned updater spawn) |
1 | 1 | 1 | 1 |
autoApplyUpdates |
2 | 2 | 2 | 2 |
detectPattern (the pre-0.4.16 field name) |
0 | 0 | 0 | 0 |
capture-denied (the arbitrary-file capture) |
0 | 0 | 0 | 1 |
withFileTypes (a directory walker) |
0 | 1 | 1 | 2 |
diagnostic/upload |
1 | 1 | 1 | 1 |
Read across the middle two columns: nothing competitor-facing moves. The detection field, the account flag, the signed engine, its key, and the unsigned updater are all exactly where they were in 0.4.16 and exactly where they are in 0.4.20.
capture-denied is 0 in both builds I hold, and first appears in 0.4.20. The arbitrary-file
capture I wrote about on 2026-07-09 really does arrive in 0.4.20, and that is now measured against
the builds on either side of it instead of taken from the 0.4.20 build alone.
The 0.4.19 column also reproduces the 0.4.19 column of the marker table in the v0.4.26 update, which was measured separately.
A string-literal set comparison of 0.4.16 against 0.4.18, and 0.4.18 against 0.4.19, returns nothing of substance either way: every string that appears is matched by one that disappears, differing only in minified identifiers. 0.4.19 is 1,123 bytes larger than 0.4.18 and introduces no new string.
The one real change in the gap: an in-app support report, and an add-on list
0.4.18’s index.js is 4,151 bytes larger than 0.4.16’s. That growth is one feature.
| marker | 0.4.16 | 0.4.18 | 0.4.19 | 0.4.20 |
|---|---|---|---|---|
report/submit |
0 | 1 | 1 | 1 |
includeModList |
0 | 5 | 5 | 5 |
modList |
0 | 1 | 1 | 1 |
The app gained a “report a problem” path that posts to report/submit, and with it a routine that
walks Interface\AddOns on every WoW client you have configured and reads each add-on’s .toc for
its title and version:
function Eo() {
const e = [];
const n = (D().wowPaths ?? []).flatMap((s) => _e(s));
for (const s of n) {
const o = E(s.path, "Interface", "AddOns");
a = gt(o, { withFileTypes: !0 });
for (const r of a) {
if (!r.isDirectory()) continue;
const c = r.name;
const p = ge(E(o, c, `${c}.toc`), "utf8");
u = p.match(/^##\s*Title:\s*(.+)$/mi);
h = p.match(/^##\s*Version:\s*(.+)$/mi);
e.push({ client: ..., name: c, title: i, version: l });
}
}
return e;
}
That is a list of every add-on you have, by folder name, display title and version. Mine is in it.
This is the withFileTypes that goes from 0 to 1 in the table above.
It is driven by you, and I checked that rather than assuming it. Eo() has exactly one call
site, inside if (t) where t is includeModList. The submitter around it refuses to do anything
without a typed description (“Please describe the problem.”) and a signed-in token, and tags what it
sends source: "in-app". Nothing in the companion:request server dispatch reaches it. So the
add-on list is gathered when you fill in the form and tick the box, and not otherwise.
I am not going to dress this up. A support tool that lists your add-ons is an ordinary support tool. The narrow, true statement is: since this build, the Companion has been able to enumerate every add-on on your disk by name and version, and it does so when you submit a report with the box ticked.
Which build it arrived in, I cannot tell you. It is not in 0.4.16 and it is in 0.4.18. 0.4.17 is the build I do not have. It arrived in one of those two and I am not going to pick one.
This corrects a sentence in my previous update
The v0.4.26 update has a section headed “What replaced it: the user picks the files”, framing the
user-driven report path as what stepped in after the server-triggered capture was removed. It also
says excludeMods is “new alongside it: the mod list is still gathered, but the user can drop
entries from it before it is sent.”
That framing is wrong, and it is wrong in GSE’s favour rather than mine.
The user-driven report path did not replace the capture. It came first. report/submit,
includeModList and the add-on list are in 0.4.18 — two builds before the capture existed at all.
0.4.26 did not build a user-driven path to fill the hole left by the capture. The user-driven path
was already there, and 0.4.26 removed the server-triggered one from around it. excludeMods and
userFilePaths are the parts that are genuinely new.
Read that way, the design 0.4.26 lands on is close to the one the app already had in 0.4.18, and the server-triggered capture of 0.4.20 through 0.4.24 looks like the departure from it rather than the normal state being partly walked back. I would not have seen that without going into the builds I had skipped, and I am not going to leave it out because it reads better for GSE than for me.
What I am not claiming
- I am not claiming anything about 0.4.17. I do not have it. Not “I checked and it was clean” — I could not check. Anything that shipped in 0.4.17 and was gone again by 0.4.18 is invisible to me, and I have no way to rule it out. The same goes for 0.4.25.
- I am not claiming the add-on list is surveillance. It runs when you submit a support report with the box ticked, and not on a timer, and not on anything the server sends. I looked for a server route into it specifically, because I would have had every reason to want one, and there is not one.
- I am not claiming the gap was hidden from anyone. It was my gap. GSE shipped the builds; I did not write about them.
- I did not diff the renderer or the preload bundle, only
out/main/index.js. A change confined to the report form’s own UI would live there and I have not read it. It cannot give the app a file or network capability the main process does not already expose, but I did not check it. - I did not run either build. This is static reading only. No runtime capture, no live traffic.
- I did not compare 0.4.18’s installer payload beyond
app.asar— the launcher and the native modules are unexamined.
How to verify this yourself
- Get the installers.
gse.tools/releasesmay only offer the current build; if you have older copies, check them against the SHA-256 in the table above before reading anything. - Extract the app logic in two stages, then unpack it:
7z e "GSE Companion Setup 0.4.18.exe" -oex "$PLUGINSDIR/app-64.7z", then7z e "ex\app-64.7z" -oasar "resources/app.asar", thennpx --yes @electron/asar extract asar\app.asar out. The logic isout/out/main/index.js. - Check what the build says it is, not what the file is called. Open
out/package.jsonand read"version". This is the step I skipped, and it is the reason for the correction at the top. - Beautify with
npx js-beautifyso the line numbers line up, then count the markers in the table:integrityRef,restrictedAccount,b531cb8b,sign.detached.verify,force-run,autoApplyUpdates,capture-denied,withFileTypes. Compare 0.4.16, 0.4.18, 0.4.19 and 0.4.20. - Confirm
capture-deniedreturns nothing in 0.4.18 and 0.4.19 and one hit in 0.4.20. - For the add-on list, search
includeModList, find the function it gates, and confirm that function has exactly one call site and that the caller demands a description and a token. Then searchcompanion:requestand confirm none of its branches reach it. - Run a control on every search that returns zero. A filter that cannot match looks exactly like a
clean scan.
restrictedAccountreturning 5 on the same file with the same tool is a good one.
Method corrections carried from this pass
- A filename is not a build. I had 0.4.18 twice and called one of them 0.4.17, then reported
that “0.4.17 and 0.4.18 are identical” — which is true of the two files and says nothing about
the two builds.
package.jsoncarries the version and I did not read it until after I had made the claim. Every build in this repository is now identified from inside the archive. - “Every build since” was a completeness claim I had not checked. It sat in the README’s opening note for five weeks. The gap it covered was three builds wide, and two of the three were sitting in my downloads folder the whole time.
- A gap I did not notice is still a gap. This is the third time I have written that sentence in
this repository — the unread
idxbranch, the unexamined bridge add-on, and now this. The pattern is the same each time: a claim about coverage made from memory rather than from a list.