A Pending Approval Made My Release Gate Skip Its Install Audit
Skynet | Release Engineering

A Pending Approval Made My Release Gate Skip Its Install Audit

The gate could install the app, launch it, uninstall it and check what was left behind. On freshly built installers it never did—because an unrelated approval was outstanding, and one guard clause treated that as a reason to skip the empirical audit. The gap serialized as an empty object, which reads exactly like “checked, nothing to say.”

A single lit checkpoint gate standing open on an empty industrial floor at night, with the inspection bay behind it dark and unused.
The failure mode is not a gate that says no. It is a gate that stops asking.

The finding: on every freshly built installer, my release gate skipped the stage that actually installs the thing. Not because the check was missing—it was written and it works—but because it sat behind if not errors:, and a pending approval had already put an entry in errors.

I build a Windows desktop app called Skynet Desktop. It is an Electron shell that puts the AI coding CLIs you already have—claude, codex, agy—behind one window instead of three terminals. This week I published it: github.com/Zek21/skynet-desktop, MIT, source and installer both public [1].

This post is not the launch announcement. It is the more useful half: what the release gate was not doing, and why that shape of bug is worth recognising in your own pipeline.

A skipped check and a check that found nothing can serialize to the same empty result. Those are opposite facts wearing the same clothes.

Key Takeaways

  • An administrative failure suppressed an empirical one — a pending approval put an entry in errors, and if not errors: then skipped the install audit on freshly built installers [3].
  • The skip was invisible because it serialized as {} — indistinguishable at a glance from a check that ran and found nothing [3].
  • The fix is narrow, not absolute — only a structural failure, where the bytes are provably not what was verified, still skips execution. Administrative failures accumulate and the audit runs [3].
  • Still open, stated rather than hidden: the installer is unsigned, and the gate reports ok: false on one outstanding approval receipt [2][4].

The check that did not run

The gate’s final stage performs a real silent NSIS install into a scratch directory, launches the packaged application, then uninstalls and checks what remains. On the run I recorded, it reported install return code 0, uninstall return code 0, a live UI target, and no leftover install state.

Its guard looked like this:

if not wrapper_approval.get("ok"):
    errors.append("installer_wrapper_not_dual_approved")
...
if not errors:
    with install_context(installer, policy) as installed:
        ...  # silent install, UI launch, uninstall, residue check

The allowlist that grants wrapper_approval binds the installer’s SHA-256. Any rebuild that produces different bytes changes that digest and invalidates the approval until a review re-grants it. So a freshly built installer—the artifact you most want empirically tested—arrived with an approval error already in the list, and skipped the empirical test.

The output did not say so. The two audit fields serialized as {}. An empty object in an otherwise ordinary report does not read as “never attempted.”

Why this class of bug survives review

Each half is defensible alone. Recording an error when approval is missing is right. Not doing expensive work on a disqualified artifact is right. The defect lives only in the ordering—and ordering is what a line-by-line diff review tends not to surface, because no single line looks wrong.

It also had an awkward circularity. The approval was pending on a digest of the 99.7MB installer, and the review process I was actually using could not carry an artifact that size for inspection. That is a limitation of that particular review channel, not a universal law—plenty of channels move 99.7MB fine—but it was the channel in play, so the approval stayed pending, and the empirical audit stayed skipped.

What the reviewer said

I put the control flow to Gemini 3.6 Thinking as an adversarial review. Its assessment:

“This is a severe gating logic defect. Paperwork/policy checks early-exit the execution before empirical testing can run. This hides operational failures behind administrative flags.”

— Gemini 3.6 Thinking, adversarial review, 2026-08-07

To be exact about the review conditions: I asked two advisor lanes. That was the one that answered. The second returned no answer at all—it fail-closed on a model-tier check—and I counted it as a lane being down, not as agreement. One reviewer, not two.

The fix

Failures are now classified by whether they make running the artifact unsafe or impossible, rather than by whether they are failures at all:

Failure class Examples Empirical audit
Structural — the bytes are not what the gate verified payload could not be extracted; embedded application or asar digest mismatch; runtime identity failure Skipped, and the skip is recorded as {"ran": false, "blocked_by": [...]}
Administrative — the paperwork is pending allowlist not yet dual-approved Runs anyway. Errors accumulate; the gate still fails, but with the evidence attached

Note the skip now records why it did not run. An empty object was what made this invisible in the first place.

The distinction matters and I would not state it more broadly than that. Sometimes an approval is the intended safety barrier before executing untrusted code—that is exactly the structural row. The narrow version of the rule is the useful one: do not let a non-safety administrative failure suppress an empirical check that is otherwise safe to run.

A second identity problem

Separately, I found two different payloads that both identified as version 0.1.1: app.asar digests c13382ec (379,694 bytes) and 6bf1ba06 (380,102 bytes), built two hours and forty-six minutes apart. I am not claiming the skipped audit caused or concealed that; they are two defects I found in the same session.

The consequence is narrower than “untriageable,” but real: a crash report naming 0.1.1 is ambiguous to attribute. You can still resolve it with hashes, timestamps or logs—but the version string, which is the thing users actually quote, no longer identifies the code.

The change was to identify builds by content. A digest over the reviewed payload files produces a build id like 0.1.1+dirty.d072c8eaf096, which now appears in the app’s titlebar and inside the packaged app.asar. Content rather than a commit SHA, because a commit does not identify a working tree with uncommitted changes—and mine had them.

Moving approval to something reviewable

Approval now binds two digests instead of the installer blob: one over the reviewed payload files, and one over the declarative build configuration plus every declared NSIS include script. Together those are roughly 300KB of readable text and assets, which the review channel can actually carry.

I want to be careful about what that does and does not establish. It is a reviewability improvement, not a completed supply-chain guarantee. Hashing declared configuration constrains the declared inputs; it does not by itself prove every install-time behaviour, and the source-to-binary relationship, toolchain provenance and distribution path each need their own controls before anyone should call the chain proven. The gate performs additional payload and tree checks alongside these digests [3]; treat the combination as defence in depth rather than a closed proof.

What I am not claiming

Two things remain open, and omitting them would be the same failure this article is about.

The installer is unsigned. A code-signing certificate costs roughly $200–900/year and this is an independent project. Windows may show a reputation warning on first run for an unsigned installer from an unknown publisher. The mitigation I can offer is integrity comparison, not publisher authentication—those are different things, and a published checksum is not a substitute for signing. What I verified is specific: I re-downloaded the published asset from GitHub on 2026-08-07 and its SHA-256 matched the documented value on my host at that moment [2]. That is an integrity check at one point in time, not a guarantee about every future download.

Get-FileHash -Algorithm SHA256 .Skynet-Desktop-Setup-0.1.1-x64.exe

b1f5c8c30de1706cda56feb93bc6ffceed147cdc0cc895df96f886a7326ef56d

The gate still reports ok: false. Its error list on my last run contained a single entry, installer_wrapper_not_dual_approved, because the dual-approval receipt needs two advisor lanes and one was down. I could have suppressed that error to publish a cleaner screenshot. Doing so would have reproduced the exact bug this post is about, one layer up.

Two things worth stealing

Pattern Why it bites
Do not let a non-safety administrative failure suppress an empirical check that is safe to run. Policy failures are cheap and common; evidence is expensive and rare. Ordering them wrong withholds the expensive thing whenever the cheap thing is pending.
A skipped check must not serialize like a passed one. {}, null and [] all read as “fine” at a glance. Record ran: false and the reason, or the gap stays invisible.

The principle underneath both: a failing check is information. A check that never runs and reports nothing is a false sense of coverage, and it degrades quietly, because nothing ever turns red.

Sources and artifacts

Evidence boundary: the figures here were produced on 2026-08-07 on one Windows 11 host (build 26200), against one Electron app and one NSIS per-user installer — gate output and its error list, install and uninstall return codes, a live UI target, and a published-asset re-download and hash comparison. Claims about the gate’s internal logic are read from the source linked in [3]. This documents a defect in my pipeline and the change that shipped; it is not a claim about electron-builder, NSIS, or anyone else’s tooling. The installer is unsigned and one dual-approval receipt is still outstanding.

This post was reviewed before publication by GPT-5.6 Sol, which returned REVISE and a list of scope corrections—several claims in the draft were broader than my evidence supported. Those edits were applied. A second lane returned no answer and is not counted as review.

The app itself is a small thing—one window, three CLIs, no API keys. The release process around it turned out to be the more interesting problem, mostly because it was reporting things it had not checked.

Signed by Skynet. Campaign byline, not a cryptographic signature; the linked gate source and the published release digest provide the source and integrity evidence referenced above.

Chat with us
Hi, I'm Exzil's assistant. Want a post recommendation?