# Windows Release

> Build and verify the Windows desktop build (NSIS installer + latest.yml) for the canonical OpenMausBot release and its legacy updater mirror. Use when cutting a release, shipping a new version to Windows users, or when a Windows user reports they are stuck on an old version. Windows only — does not cover the macOS dmg/notarization flow.

- Skill: `milind-soni/windows-release` (Agent Skill)
- Install (CLI): `npx skillmds@latest add milind-soni/windows-release`
- Raw SKILL.md: https://api.skillmd.com/api/skills/milind-soni/windows-release/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: milind-soni (https://skillmd.com/u/milind-soni)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/milind-soni/windows-release

---


# Windows release

Ships `OpenMausBot-<version>-setup.exe` and its update feed to
[milind-soni/OpenMausBot](https://github.com/milind-soni/OpenMausBot/releases).
The unified release workflow mirrors the same bytes to the legacy releases
repository for apps installed before the updater migration.

**Scope: Windows only.** The macOS build is a separate flow (dmg + notarytool +
staple) that must run on a Mac. This skill never touches mac artifacts — but see
[Every release ships both](#every-release-ships-both) before you finish.

## Preconditions

- **Run on Windows.** NSIS packaging from macOS needs Wine; don't.
- **Node 24+** (`package.json` `engines`). Node 23 builds fine but pnpm warns on
  every step and CI runs 24 — don't debug a runtime oddity on the wrong major.
- **pnpm** via `corepack pnpm`. If `corepack enable` fails with EPERM (no admin),
  drop a `pnpm.cmd` shim containing `@echo off` / `corepack pnpm %*` somewhere on
  PATH — `package:win` chains `pnpm build && …` and needs bare `pnpm` to resolve.

## 1. Version

Bump `version` in `package.json`. It must match the tag on the GitHub release you
upload to, and it becomes the version electron-updater compares against.

## 2. Build

```powershell
pnpm install
pnpm typecheck
pnpm package:win
```

`package:win` deliberately omits `build:speech` — the dictation helper is a signed
macOS Swift binary and has no Windows counterpart.

Output in `release/`:

| File | Purpose |
|---|---|
| `OpenMausBot-<version>-setup.exe` | the installer |
| `latest.yml` | **the update feed** — see step 4 |
| `OpenMausBot-<version>-setup.exe.blockmap` | differential updates |
| `OpenMausBot-<version>-x64.zip` | portable, not used by the updater |

## 3. Verify before uploading

Three things silently produce a broken app if wrong. Check all three:

```powershell
Test-Path release\win-unpacked\resources\server\index.js   # harness server
Test-Path release\win-unpacked\resources\ui\index.html     # built UI
Get-Content release\win-unpacked\resources\app-update.yml  # feed config
```

- Missing `server/index.js` → `utilityProcess.fork` fails → the 🐭 "Couldn't start
  the bot server" page.
- Missing `ui/index.html` → server has nothing to serve → black window.
- `app-update.yml` must point at `milind-soni/OpenMausBot` and, while the
  build is unsigned, **must not contain `publisherName`** — electron-updater would
  reject every update as untrusted.

Then smoke-test the installer itself. Run it, and confirm:

1. It installs per-user with no UAC prompt and launches.
2. The chat window renders (not the error page). Server logs land in
   `%APPDATA%\OpenMausBot\logs\server.log`.
3. The model picker lists at least one provider — this exercises the `.cmd`-shim
   resolution in `server/procs.ts`, which only ever runs for real on Windows.
4. No update popup appears on launch. Background check failures are silent by
   design; a popup here means that regressed.

## 4. Publish

Upload to the **same tag** as the macOS release for that version, so one release
carries both platforms.

```powershell
Copy-Item release/OpenMausBot-<version>-setup.exe release/OpenMausBot-setup.exe
gh release upload v<version> --repo milind-soni/OpenMausBot `
  release/OpenMausBot-<version>-setup.exe `
  release/OpenMausBot-setup.exe `
  release/OpenMausBot-<version>-setup.exe.blockmap `
  release/latest.yml
```

Prefer the repository's **Release** workflow, which builds all platforms from
one pinned commit and mirrors the complete, byte-identical asset set safely.
If this emergency manual path is used, the same four files must also be attached
to the matching draft in `milind-soni/openmausbot-releases`; never replace the
bytes of an already-published asset.

Both names are required, for different consumers:

- **`OpenMausBot-<version>-setup.exe`** is what `latest.yml` references by name and
  sha512. The auto-updater downloads exactly this.
- **`OpenMausBot-setup.exe`** is a byte-identical copy that gives the README's
  `/releases/latest/download/OpenMausBot-setup.exe` button a stable URL. This
  mirrors `OpenMausBot.dmg` sitting beside `OpenMausBot-<version>.dmg`.

### latest.yml is not optional

Without it every installed Windows app 404s on check and stays on its version
forever. It is generated by `package:win` even under `--publish never`.

**Never hand-edit it or carry one forward from a previous build.** It pins the
installer's sha512; a mismatch makes the updater download and then reject the
update, which looks like "updates silently do nothing".

## Every release ships both

A version that exists on macOS but not on this release is a Windows user stuck on
old code with no signal that anything is wrong — the updater reports "up to date"
because `latest.yml` still describes the older build.

So: **whenever a new version goes out, this flow runs too.** If Windows can't ship
for some reason, don't publish the mac-only release under a new version tag either
— or accept that Windows is knowingly frozen and say so in the release notes.

Because the two builds must run on two machines, the tag is the join point: cut the
release, attach mac artifacts from the Mac, attach Windows artifacts from here.

## Known: the build is unsigned

No certificate is configured, so SmartScreen shows "unknown publisher" and users
click **More info → Run anyway**. The README documents this. Auto-update still
works *because* it's unsigned (no `publisherName` to verify against).

If signing is added later, it goes under `win.signtoolOptions` or
`win.azureSignOptions` in `electron-builder.yml` — electron-builder 26 nests these;
there is no top-level `win.certificateFile`. Once signed, keep the certificate
subject stable forever, or list both old and new in `publisherName`; changing it
strands every already-installed user.

