Skip to the content.

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

How to verify this yourself

  1. Get the installers. gse.tools/releases may only offer the current build; if you have older copies, check them against the SHA-256 in the table above before reading anything.
  2. Extract the app logic in two stages, then unpack it: 7z e "GSE Companion Setup 0.4.18.exe" -oex "$PLUGINSDIR/app-64.7z", then 7z e "ex\app-64.7z" -oasar "resources/app.asar", then npx --yes @electron/asar extract asar\app.asar out. The logic is out/out/main/index.js.
  3. Check what the build says it is, not what the file is called. Open out/package.json and read "version". This is the step I skipped, and it is the reason for the correction at the top.
  4. Beautify with npx js-beautify so 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.
  5. Confirm capture-denied returns nothing in 0.4.18 and 0.4.19 and one hit in 0.4.20.
  6. 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 search companion:request and confirm none of its branches reach it.
  7. Run a control on every search that returns zero. A filter that cannot match looks exactly like a clean scan. restrictedAccount returning 5 on the same file with the same tool is a good one.

Method corrections carried from this pass

  1. 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.json carries 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.
  2. “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.
  3. 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 idx branch, 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.