Update 2026-07-17 (later) — Companion v0.4.26 removes the file capture, and addon 3.3.25 makes the error-log upload opt-in
Date: 2026-07-17 Author: Jesper (JesperLive / MrSataana), developer of GRIP - Enhanced Macro Sequencer (GRIP-EMS) Subject: GSE Companion v0.4.26, and GSE addon build 3.3.25 Previous update: UPDATE-2026-07-17-v0.4.24.md
My last update, written earlier today, ended on this sentence: GSE removed the ability to destroy a competitor’s data and kept the ability to collect it. That is no longer accurate, and this update exists to say so.
v0.4.26 removes the collection half. The unsigned, server-triggered routine that read arbitrary files under Interface\AddOns and WTF and uploaded them is gone. The server can no longer name a file. The paths field it used is not read anywhere in the build, the recursive directory walker that resolved bare filenames has been deleted, and the capture-denied machinery around it went with them. In its place the server can set one boolean, requestFiles, whose entire effect is a desktop notification asking you to attach files yourself through the “Report a problem” form.
Addon 3.3.25 does the matching half in-game. The BugSack/BugGrabber error-log upload — which 0.4.22 had added to the always-on gather — is now an unticked checkbox in the support window, and the Companion strips errorlogs out of any server-triggered gather regardless.
Bearing: this is the finding I have been asking for. Taken with the v0.4.24 write guard, both halves of the competitor-facing subsystem I documented in June are now gone from the shipped client: it can no longer write to my addon’s files, and it can no longer read them on the server’s instruction. I have spent five updates describing what this client could do to my data. It is only fair to be equally precise now that the answer has changed.
What has not changed, and I am not going to bury it: the unsigned auto-updater is untouched, and it remains the largest capability in the app. The signed engine’s read operation still has no basename guard. And one indirect path to my addon’s contents survives — described in full below, because a removal I overstate is worth as little as a risk I understate.
Chain of custody
| Artifact | SHA-256 | Size |
|---|---|---|
| GSE Companion Setup 0.4.26.exe | c720ec821818fa2b58a4e50d71dbbd0c06c81c01ec573b5e2a4505554d0780d7 |
81,327,286 B |
| resources/app.asar (from the installer) | c5e569a768acf03bfbe7fe8aa9a9d6a9c4a52f534fa913cc374f90534a57ac21 |
6,210,073 B |
| out/main/index.js (inside that asar) | db3f8f5c1d747d05c701cfb0e05538a065c90fbf14d51535b6e3ca882f53b7e3 |
136,850 B |
| GSE-3.3.25.zip (free build) | ea15d65ba23f91eae0381b2302fabe41443829289c8cc6522dffa34c631c08eb |
2,561,801 B |
| GSE-3.3.25-PatronBuild.zip (patron build) | a62bc47e28b3ae14a7781703a76963d66e7596747bd0b7960a894e704948ab85 |
2,571,012 B |
The app.asar extracted from the 0.4.26 installer is byte-identical to the installed one at %LOCALAPPDATA%\Programs\gse-companion\resources\app.asar, so the code analysed here is the code that runs on my machine. As a control on my own method I re-hashed the 0.4.24 installer and got d912618652c9cfdb3efb5d23e9cd78ed6a3810f02dad5f41a8546c0cf381d76d, matching the value published in the previous update.
v0.4.24’s out/main/index.js was 136,477 bytes; v0.4.26’s is 136,850, a difference of 373. The asar grew by 7,532, which is mostly the renderer: out/renderer/assets/ was rebuilt under new content-hash filenames and out/preload/index.mjs grew 6,372 → 6,557 bytes. That is the new attach-files UI, and it is consistent with the change described here.
There is no 0.4.25 in this account. 0.4.24 and 0.4.26 are the two builds I hold. I do not know whether 0.4.25 was released and pulled, released and missed by me, or never released. I am recording the gap rather than papering over it.
What was removed
The server can no longer name a file
The companion:request dispatch has the same three branches as before — task (the signed engine), idx (the code self-attestation channel cleared in the previous update), and the fall-through. The fall-through is what changed:
} else if (t?.type === "companion:request")
if (t.task)
Qo(t.task).catch(() => {});
else if (Array.isArray(t.idx))
Jo(t.requestId, t.idx).catch(() => {});
else {
const n = Array.isArray(t.kinds) ? t.kinds : [];
if (console.log(`[sse] companion:request requestId=${t.requestId} kinds=${n.join(",")} requestFiles=${!!t.requestFiles}`), n.length && Ho(t.requestId, n).catch((s) => {
console.warn("[diagnostic] fulfilment failed:", s && s.message);
}), t.requestFiles) {
try {
C?.webContents.send("report:files-requested", {
note: t.note || null
});
} catch {}
try {
new bt({
title: "GSE Companion",
body: 'Support has asked you to attach files to your report — open "Report a problem" to add them.'
}).show();
} catch {}
}
}
In v0.4.24 this branch read Qo(t.requestId, t.kinds || [], t.paths || []) — three arguments, the third server-supplied. In v0.4.26 it reads Ho(t.requestId, n) — two, and n is t.kinds. The gather function’s signature lost its paths parameter to match.
The string .paths does not occur anywhere in the 0.4.26 build. In 0.4.20 through 0.4.24 it occurred twice.
kinds cannot smuggle a path
The obvious question is whether the capability just moved into kinds. It did not. Ho is the whole server-triggered path:
async function Ho(e, t) {
if (!e) return;
if (!_) { console.warn("[diagnostic] no auth — skipping"); return; }
const n = new Set((Array.isArray(t) ? t : []).map((r) => String(r).toLowerCase()));
n.delete("errorlogs");
const s = ys(n);
...
and ys treats kinds as a closed set of three flags, not as data:
- always:
Po()— GSE’s own SavedVariables (see below) errorlogs→Co()— butHodeleted that key one line earlier, so it is unreachable from the server pathlog→ the in-memory console ringsettings→settings.jsonwithaccessTokenanduserSessiondeleted first
There is no branch anywhere in ys that treats a member of kinds as a filesystem path. An unrecognised kind is ignored.
The walker and its caps are gone
qo — the function that resolved server-supplied entries — still exists by name, but it is a different function with one caller. The recursive directory walker it used to call (function(e,t,n,s){ ... V.isDirectory() ? recurse : name === target && push }) has been deleted outright. withFileTypes occurrences drop from 2 to 1; the survivor is the mod-list reader, which reads directory names and .toc headers, not file bodies. The 40,000-walk-entry cap (4e4) is gone because there is no longer a walk to cap.
Marker counts, 0.4.19 → 0.4.26
Produced by marker_scan.py (extended for this build; the tool is in the audit set, not written for this document):
| marker | 0.4.19 | 0.4.20 | 0.4.21 | 0.4.22 | 0.4.23 | 0.4.24 | 0.4.26 |
|---|---|---|---|---|---|---|---|
capture-denied |
0 | 1 | 1 | 1 | 1 | 1 | 0 |
capture- |
0 | 3 | 3 | 3 | 3 | 3 | 0 |
.paths |
0 | 2 | 2 | 2 | 2 | 2 | 0 |
4e4 (walk cap) |
0 | 1 | 1 | 1 | 1 | 1 | 0 |
withFileTypes |
1 | 2 | 2 | 2 | 2 | 2 | 1 |
userFile: |
0 | 0 | 0 | 0 | 0 | 0 | 1 |
requestFiles |
0 | 0 | 0 | 0 | 0 | 0 | 3 |
userFilePaths |
0 | 0 | 0 | 0 | 0 | 0 | 4 |
excludeMods |
0 | 0 | 0 | 0 | 0 | 0 | 4 |
includeErrorLogs |
0 | 0 | 0 | 0 | 0 | 0 | 5 |
report:files-requested |
0 | 0 | 0 | 0 | 0 | 0 | 1 |
| ed25519 key | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
sign.detached.verify |
1 | 1 | 1 | 1 | 1 | 1 | 1 |
--force-run |
1 | 1 | 1 | 1 | 1 | 1 | 1 |
autoApplyUpdates |
2 | 2 | 2 | 2 | 2 | 2 | 2 |
integrityRef |
4 | 4 | 4 | 4 | 0 | 0 | 0 |
restrictedAccount |
5 | 5 | 5 | 5 | 0 | 0 | 0 |
The four GRIP-EMS identifiers remain absent in plain, base64, hex and reversed encodings across every build tested.
What replaced it: the user picks the files
The attach path is user-driven end to end, and the chain is short enough to give in full.
report:submit is an IPC handler — it is called by the renderer, which is the local UI, with source: "in-app":
O.handle("report:submit", async (e, t) => As({
description: t?.description,
includeModList: !!t?.includeModList,
excludeMods: Array.isArray(t?.excludeMods) ? t.excludeMods : [],
includeErrorLogs: !!t?.includeErrorLogs,
userFilePaths: Array.isArray(t?.userFilePaths) ? t.userFilePaths : [],
source: "in-app"
}));
and As assembles the report:
const c = new Set(["gsesv", "companionsv", "log", "settings"]);
s && c.add("errorlogs"); // s = includeErrorLogs
let i = [];
try { i = ys(c); } catch {}
if (Array.isArray(o) && o.length) // o = userFilePaths
try { i.push(...qo(o)); } catch {}
let l = [];
if (t) // t = includeModList
try {
const h = new Set((Array.isArray(n) ? n : []).map(String));
l = ws().filter((y) => !h.has(y.name)); // n = excludeMods
} catch {}
qo is the only consumer of userFilePaths, and it has exactly one call site — the line above. It still enforces the realpath scope check against Interface\AddOns and WTF and a 40-file cap, and it tags what it collects userFile: rather than capture-.
As has two call sites, not one, and I checked the second only on a later pass. The other is fr() at line 4091, which is the in-game report path and is the missing link between this build and addon 3.3.25:
if (g.GSESupportReports) // g = the parsed SavedVariables/GSE.lua
try { await fr(g.GSESupportReports); } catch (m) { ... } // line 2950
...
async function fr(e) { // line 4091
...
const a = await As({
description: r,
includeModList: !!s.includeMods,
includeErrorLogs: !!s.includeErrorLogs,
source: "in-game"
});
So the full chain of the support-request restructure is: you fill in GSE’s in-game support window, which writes GSESupportReports (a SavedVariable that is new in 3.3.25); the Companion’s sync loop reads SavedVariables/GSE.lua, finds the queue, and uploads each entry, deduplicating on uploadedSupportReportIds. That is why the error-log consent had to be captured in-game: the in-game form is what the Companion uploads, so the tick-box has to live where the report is written.
It does not weaken the user-driven conclusion, and I checked rather than assumed: fr passes no userFilePaths, so qo is unreachable from it, and its includeErrorLogs is the value of the tick-box the user set in-game. Both callers of As are user-initiated — one from the Companion’s own form, one from the game’s. Neither takes a path from the server.
The honest note is about method rather than result: I published the “user-driven end to end” claim above having enumerated one of the two call sites. The claim survived the second one. It did not have to.
So a user can still attach GRIP-EMS.lua to a GSE support report. The difference is that a user choosing to send me-shaped data to GSE is a user exercising judgement about their own files, which was never the thing I objected to. The server choosing it for them was.
excludeMods is new alongside it: the mod list is still gathered, but the user can drop entries from it before it is sent.
The addon half: 3.3.25
Measured by diffing the two zips against 3.3.24-2 rather than reading the changelog.
- Free build: 165 files, unchanged. Patron build: 168 → 169. The single added file is
release.json, a BigWigs packager manifest. It is a packaging artifact, not code — it names the free zip’s filename inside the patron zip, which is what a packager manifest does. The patron-only set is otherwise the same threeGSE_QoLfiles as 3.3.24-2, so the pay-gate structure documented in the previous update is unchanged. - Ten files differ from 3.3.24-2 in the patron build, nine in the free build. Six are
.tocversion strings in the patron build, five in the free. The substantive ones areGSE_Options/Support.lua(+406 B),GSE/Localization/ModL_enUS.lua(+103 B),GSE_Utils/Patrons.lua(+741 B, a patron-name list), andCHANGELOG.md.
The Support.lua change is the whole in-game finding:
+ local errorLogsCheck = UI:Create("CheckBox")
+ errorLogsCheck:SetType("checkbox")
+ errorLogsCheck:SetLabel(L["Include my BugSack / BugGrabber error logs (recommended if you are seeing in-game errors)"])
+ errorLogsCheck:SetValue(false)
+ frame:AddChild(errorLogsCheck)
with includeErrorLogs = errorLogsCheck:GetValue() and true or false added to the submitted payload, and the box reset to false after each send. It defaults off.
This directly answers the 0.4.22 finding in hashes.txt: “0.4.22 adds a BugGrabber/BugSack SavedVariables reader to the always-on gather, so their error-log content is attached to every diagnostic upload.” It is now neither always-on nor server-reachable.
Re-verified directly on 3.3.25, both builds:
- Competitor-schema scan (
provenanceSource,gse-legacy,GRIP-EMS,GRIP_EMS,restrictedAccount,integrityRef,detectGrip,purgeGrip) across every.lua: 0 hits in the patron build, 0 in the free build. Verified against a control — a case-insensitivegripsearch on the same corpus returns 5 hits, all resize-grip UI comments — so the zero is a real zero and not a search that failed to run. - Codec unchanged.
GSE/API/Serialisation.lua:8still writes the plain format:"!GSE3!" .. C_EncodingUtil.EncodeBase64(C_EncodingUtil.CompressString(C_EncodingUtil.SerializeCBOR(tab))). Every one of the 18GSE3occurrences that mentions!GSE3!+is a comparison or decode position (Serialisation.lua:14,Utils.lua:435/481/493/506). The ChaCha20 lock-out format remains provisioned and never written, as at 3.3.22-12, 3.3.23-7, 3.3.24-1 and 3.3.24-2. Serialisation.lua,Codec.luaandPlugins.luaare byte-identical to 3.3.24-2. The locked global proxy is unchanged.
The in-game addon remains inert with respect to competitor targeting.
What did not change
The updater, which is still the largest capability in the app
Unchanged on every one of the five properties from the previous update’s teardown, re-checked line by line rather than carried forward:
| Property | 0.4.24 | 0.4.26 |
|---|---|---|
Asset selection reads only fileWin/fileMac/fileLinux, no digest/hash/signature |
yes | yes (L5274-5275) |
autoApplyUpdates defaults to !0, gate is !== !1 |
yes | yes (L74, L5208) |
Spawns the installer with /S --force-run |
yes | yes (L5240) |
| HTTP 426 on a sync triggers an out-of-band release check; exactly 2 occurrences | yes | yes (L1992 test, L2003 rethrow) |
content-length feeds the progress bar and is compared to nothing |
yes | yes (L5290) |
The 0.4.26 installer is unsigned (Get-AuthenticodeSignature → NotSigned), as 0.4.23 and 0.4.24 were.
So the asymmetry from the previous update stands, and is now starker rather than softer. The two competitor-facing capabilities have both been removed. The capability that can download an executable from a server-named asset id and run it silently as you, with no signature check and no hash check, is exactly where it was. It affects every GSE user identically, including GSE’s own, and it is still the answer to “what can the server make this app do”.
The signed engine can still read my addon’s file
So — the engine’s read primitive — has only the path-scope check:
function So(e, t) {
if (!fs(e, t)) throw new Error("path out of scope");
return $e(e);
}
The write guard added in 0.4.24 is retained and unchanged, one function below it:
const ko = /^GSE.*\.lua$/i;
function $o(e, t, n) {
if (!fs(e, n)) throw new Error("path out of scope");
if (!ko.test(ze(e))) throw new Error("write refused: not a GSE SavedVariables file");
GRIP-EMS.lua still fails the write guard and still passes the read scope check.
The one indirect path that survives
With the capture gone, this is the only route from GSE’s server to the contents of my addon’s save file, and it is worth stating precisely because it is easy to overstate in either direction.
ys calls Po() unconditionally, on every gather, including the server-triggered one. Po() collects every file matching Ao = /^GSE.*\.lua$/i plus GSE’s own GSE_Companion_Data.lua, and Ho uploads the result to /diagnostic/upload. A companion:request with any non-empty kinds therefore uploads GSE’s own SavedVariables with no user prompt. That is unchanged from 0.4.24, and on its own it is unremarkable — those are GSE’s files.
The chain is that the signed engine can read GRIP-EMS.lua into a binding and write it into a file named GSE*.lua, which then satisfies Po()’s filter on the next server-triggered gather.
Four things bound this, and all four matter:
- It requires a validly ed25519-signed directive. The key is embedded (
b531cb8b…683a), the signature check has exactly one call site, and nothing else reaches the write. - It is two steps, not one — a signed write, then a triggered gather.
- I noted the read-then-write laundering possibility in the v0.4.24 update already. What changed is its standing: it was a footnote when a one-step unsigned capture could do the same job directly. It is now the only path there is.
- I have never observed a directive of any kind being sent, in this or any capture.
I am not presenting this as a new capability. It is an old one that the removals have left exposed as the last of its kind.
The bridge addon, which I should have written about five builds ago
While auditing this build I looked at something I had never examined: Interface\AddOns\GSE_Companion, the addon that carries the desktop app’s data into the game. It is not new in 0.4.26 and it is not a change in this build. It is a gap in my own coverage, and the honest place for it is here rather than nowhere.
It is installed by the desktop application, not by any addon channel. The three files are copied out of a payload inside the Electron app:
function rs(e) {
const t = P(e, "Interface", "AddOns", ss); // ss = "GSE_Companion"
if (!W(P(e, "Interface", "AddOns"))) return !1;
W(t) || Ns(t, { recursive: !0 });
const n = lo(); // resources/addon
...
} catch {
s = ["GSE_Companion.toc", "Bootstrap.lua", "GSE_Companion.lua"];
}
for (const r of s) { const a = P(n, r), c = P(t, r); W(a) && Zn(a, c); }
const o = P(t, os); // GSE_Companion_Data.lua
return W(o) || Te(o, `GSECompanionData = {}\n`, { encoding: "latin1" }), !0;
}
Verified on disk: the three files in my Interface\AddOns\GSE_Companion\ are byte-identical to %LOCALAPPDATA%\Programs\gse-companion\resources\addon\. Neither the free nor the patron 3.3.25 zip contains the folder; 7z l on the installers shows the payload present and unchanged in 0.4.20, 0.4.22, 0.4.24 and 0.4.26 (Bootstrap.lua 260 B, GSE_Companion.lua 35,663 B), so it has shipped this way since at least 0.4.20.
How the bridge works. Inbound, the app writes GSE_Companion_Data.lua — a plain Lua assignment to GSECompanionData — into the addon folder, and WoW loads it as an ordinary addon file at /reload. Outbound, the app reads GSE’s SavedVariables after WoW exits; the in-game side says so in its own user-facing string: “your edit will sync back when WoW closes.” It is a sensible answer to the fact that addons cannot do network I/O.
Two observations, neither of them alarming:
GSE_Companionis on GSE’sSUBMODULESallowlist (GSE/API/Init.lua:199), so it receives the realGSEtable throughpushGSEInto— the same name-keyed side door past the locked proxy thatGSE_QoLuses, and which build 3.3.22-12 closed to everyone else “to deny in-memory scraping by third-party addons”.- I checked the obvious injection question and it is clean. The app generates Lua source that WoW executes, so escaping matters. Every key and value goes through
Xe:co(String(e)).replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\n/g,"\\n")— backslash first, then quote, then newline, which is the correct order. Quotes are escaped, so bridge content cannot break out of its string literal and inject code.\ris not escaped, which on exotic input would produce an unfinished-string syntax error and a bridge file WoW fails to load. That breaks the bridge rather than breaching it — a reliability bug, and I am recording it as one.
The one thing worth noting against Blizzard’s policy point 2 — “The programming code of an add-on must in no way be hidden or obfuscated, and must be freely accessible to and viewable by the general public” — is distribution, not obfuscation. The Lua is perfectly readable once extracted. But it is an add-on distributed inside an .exe rather than as add-on code, so reading it requires knowing it is in there. It is a comment on the text, not a ruling. Blizzard decides what its policy means.
A note on that comparison, added later the same day. An earlier version of this paragraph said the point was “the same shape as the GSE_QoL point-2 reading in my previous update”. That comparison is withdrawn, because the GSE_QoL reading was wrong and has been retracted — GSE_QoL is public source in GSE’s own GitHub repository and has been since 2024-07-07, so point 2 is satisfied there. See the retraction in UPDATE-2026-07-17-v0.4.24.md.
The GSE_Companion observation does not fall with it — if anything the retraction sharpens it. GSE_QoL turned out to be exactly where point 2 expects add-on code to be: in the public repo. GSE_Companion is not, and I checked four ways on 2026-07-17: the repo path returns 404; TimothyLuke’s 19 public repositories contain no Companion repo; the GSE repo’s 18 branches contain no Companion branch; and GitHub global code search for GSE_Companion_Initialize and for GSECompanionData returns 0 results each. Every other GSE add-on module — GSE, GSE_GUI, GSE_LDB, GSE_Options, GSE_Utils, GSE_QoL — is public source. This one is not, and it is the one carrying the bridge to the server. That asymmetry is the whole observation, and it is still arguable rather than decisive: the installer is free, and every user has the Lua in the clear on disk once it is installed. The limit of my check is also worth stating — GitHub code search does not index everything and covers only GitHub, so I have not established that the source is unpublished everywhere, only that it is not on GitHub.
What I am not claiming
- I am not claiming the removals are cosmetic. They are real. I traced the dispatch, the gather, and every consumer of
kinds, and the capability is gone from the client, not relocated within it. I looked for the relocation specifically because I have an obvious incentive to find one. - I am not claiming GSE ever used the capture. Across four instrumented captures — 2026-07-09, 2026-07-15, and two on 2026-07-17 — I have never seen a
companion:requestof any branch, a/diagnostic/upload, or any write to my addon’s files. The subsystem was dormant every time I looked at it, and now most of it is not there to be dormant. - I am not claiming a motive, and I have less basis for one here than usual. I do not know GSE’s release dates for 0.4.24 or 0.4.26, I do not know why 0.4.25 is absent from my collection, and the client cannot tell me why anything shipped. Timing is not causation.
- I am not claiming the app is now safe. The unsigned auto-updater is a larger capability than anything this update removed, and it is untouched.
- What I am stating is narrow and checkable: the shipped v0.4.26 application contains no routine that reads a server-named file. The server-triggered gather is restricted to GSE’s own SavedVariables by a closed set of three flags. Error logs are opt-in on both ends. Attaching anything else requires a user to choose it in the UI.
How to verify this yourself
The removal (v0.4.26):
- Extract
resources/app.asarfrom the installer (7z e "<installer>.exe" -o<dir> -y, then7z e "<dir>\app-64.7z" "resources\app.asar" -o<dir> -y). Confirm the SHA-256 against the table above, and against your installed copy at%LOCALAPPDATA%\Programs\gse-companion\resources\app.asar. npx --yes @electron/asar extract app.asar out— the.unpackednative-module errors are harmless. Beautifyout/main/index.jswithnpx js-beautifyso the line numbers here line up.- Search for the literal
.paths. Expect zero hits. In 0.4.20-0.4.24 there are two. - Search for
capture-denied. Expect zero. It is present in 0.4.20-0.4.24. - Find the
companion:requestdispatch by shape (t.task/t.idx/ else). Confirm the else-branch calls the gather with two arguments and that neither ist.paths. - Read the gather (
Hoin this build). Confirmn.delete("errorlogs")on the second line of its body, and that the function it calls (ys) branches onerrorlogs/log/settingsonly — no branch treats a kind as a path. - Confirm
userFilePathsreaches the collector fromipcMain.handle("report:submit")withsource: "in-app", and that the collector has exactly one call site.
The retained updater:
- Confirm the asset selector still reads only
fileWin/fileMac/fileLinuxwith no digest field, thatautoApplyUpdatesdefaults to!0, and that the spawn is still["/S", "--force-run"]. Get-AuthenticodeSignature "GSE Companion Setup 0.4.26.exe"→NotSigned.
The addon (3.3.25):
- Diff the free and patron zips against 3.3.24-2. Expect 165 free / 169 patron, the single new file being
release.json. - Open
GSE_Options/Support.luaand confirmerrorLogsCheckis created withSetValue(false). - Confirm
GSE/API/Serialisation.lua:8still writes"!GSE3!", and that every!GSE3!+occurrence is a comparison.
Traps that cost me time on this build:
- Handler names rotate every build. In 0.4.24
Iowas the write guard’s regex; in 0.4.26Iois the BugGrabber regex andAois the/^GSE.*\.lua$/ifilter. Matching by name here would produce a confident, wrong answer. Match by shape. - A second
/^GSE.*\.lua$/istill sits inside the gather region and still looks like capture scoping. It governsPo(), the default gather of GSE’s own files. This trap survives from 0.4.24. findstrhas no alternation. Afindstr /r "^>>>|match"filter treats the|literally and silently matches nothing, which reads exactly like a clean scan. It briefly produced a false “0 competitor hits” for me on this build. Every zero in this document was re-run against a control pattern that must match, and the controls are quoted above.- Case-insensitivity, again. Searching
Homatchedho(at line 806; searchingqomatchedQo. Read the matched line to resolve case. This is the fourth build in a row this has bitten me.
Method corrections carried from this pass
- My previous update’s closing framing was overtaken within a day. “Removed the ability to destroy and kept the ability to collect” was accurate for 0.4.24 and is wrong for 0.4.26. A conclusion pinned to a build number needs re-testing per build, and a memorable sentence is the most dangerous kind to carry forward.
- A filter that cannot match looks identical to a clean result. The
findstralternation error would have published a zero I had not actually measured. Zero results now require a control that must match, run in the same command. - The bridge addon went unexamined for six builds. It is benign, and I did not know that until I read it. The same gap-shape as the
idxbranch in the previous update: not examined, therefore not claimable either way.