Verify a download.

Relay isn't notarized by Apple yet, so macOS can't vouch for it. These steps let you check it yourself: that your file is the one GitHub published, how it's signed, and that the app's code is the code in this repo at that version.

The example outputs below are from 1.8.2, when Relay was still called Agent Desk. For 1.8.x and earlier, use Agent-Desk-*.dmg and /Applications/Agent Desk.app in the commands.

1. Check the checksum

Every release on GitHub lists a SHA-256 checksum for each file. Work out the checksum of the file you downloaded:

shasum -a 256 ~/Downloads/Relay-*.dmg

It prints one line per DMG in your Downloads folder, like this for 1.8.2:

9e9daaa1ee23ff2f80a9a02ce1fc43b5d3a0de8ff1ad055333d51a5ea138ac0e  …/Agent-Desk-1.8.2-arm64.dmg

The long number should be exactly the one shown above for the latest release, or the one GitHub lists for your version (step 2). If even one character differs, don't open the file, and please report it.

2. Compare with GitHub's own record

The checksum on this site comes from GitHub, so you can also ask GitHub directly. On the Releases page, open the release's Assets: each file shows its sha256: digest. From Terminal, with the GitHub CLI:

gh release view v1.8.2 -R aahannp/relay --json assets --jq '.assets[] | .name + "  " + .digest'

Or without it:

curl -fsSL https://api.github.com/repos/aahannp/relay/releases/tags/v1.8.2 | grep -o '"digest": *"[^"]*"'

A matching checksum proves your file is the one uploaded to that GitHub release. It doesn't prove what was built into it. Steps 3 and 4 cover that.

3. Check the signature

After you've copied the app to Applications:

codesign -dv --verbose=2 "/Applications/Relay.app"

The lines that matter, from 1.8.2:

Identifier=com.aahan.agentdesk   # com.aahan.relay from 1.9.0
CodeDirectory v=20500 size=436 flags=0x10002(adhoc,runtime) hashes=3+7 location=embedded
Signature=adhoc
TeamIdentifier=not set

To check nothing in the app has changed since it was signed:

codesign --verify --deep --strict --verbose=2 "/Applications/Relay.app"

It should end with valid on disk and satisfies its Designated Requirement. Gatekeeper's own check, spctl -a -vv "/Applications/Relay.app", says rejected: that's expected until the app is notarized.

Electron's security switches (fuses) are baked into the app too. To list them:

npx --yes @electron/fuses@2.1.3 read --app "/Applications/Relay.app"

RunAsNode, NODE_OPTIONS and the inspector switches should be Disabled, and EnableEmbeddedAsarIntegrityValidation and OnlyLoadAppFromAsar Enabled, so the app refuses to run if its code bundle has been changed.

4. Check the app's code against the source

All of Relay's own code (the server, the page and the Electron shell) is plain JavaScript, packed into one file in the app: Contents/Resources/app.asar. You can unpack it and compare it with this repo at the same version. This only reads the app; it doesn't change it. You need Node.js and git.

# a new, empty folder to work in
mkdir ~/relay-verify && cd ~/relay-verify
v=$(defaults read "/Applications/Relay.app/Contents/Info" CFBundleShortVersionString)

# unpack the app's code, and fetch the source at the same tag
npx --yes @electron/asar@4.3.1 extract "/Applications/Relay.app/Contents/Resources/app.asar" app
git clone --branch "v$v" --depth 1 https://github.com/aahannp/relay src

# Relay's own code: should print "identical" five times
for d in electron lib public shell server.mjs; do diff -r "src/$d" "app/$d" && echo "$d: identical"; done

# the dependencies, installed from the same lockfile
(cd src && npm ci --omit=dev)
diff -rq src/node_modules app/node_modules | grep -v '^Only in src/node_modules' | grep -v 'package.json differ'

What we found for 1.8.2:

PartResultWhy
electron/, lib/, public/, shell/, server.mjsIdentical, byte for byteCopied as they are.
package.jsonShorterelectron-builder removes the scripts, devDependencies and build sections. Name, version and dependencies are the same.
node_modulesSame files, fewer of themelectron-builder leaves out READMEs, TypeScript typings, source maps, Monaco's esm and dev builds, and node-pty's sources and Windows builds, and trims each package's package.json.
node-pty's pty.node and spawn-helperFiles differ, machine code identicalThey're re-signed ad-hoc during the build. The last command lists these four files (arm64 and x64). The command below compares their code without the signatures.
p=node_modules/node-pty/prebuilds/darwin-arm64
for f in pty.node spawn-helper; do cmp <(otool -t "src/$p/$f" | tail -n +2) <(otool -t "app/$p/$f" | tail -n +2) && echo "$f: same code"; done

The rest of the app is Electron itself (Contents/Frameworks), from the electron 44.5.1 package on npm, re-signed ad-hoc; its machine code matched too. The small voice helper Contents/Resources/desk-listen is compiled from native/listen.swift, and rebuilding it matched on our Mac, though a different Xcode can produce different code.

5. Check the build attestation 1.9.0 and later

Since 1.9.0, GitHub Actions builds each release, and every DMG comes with a signed attestation that records which workflow, commit and runner built it. Check it with:

gh attestation verify Relay-1.9.0-arm64.dmg -R aahannp/relay

It prints nothing and exits with status 0 when the DMG matches; echo $? shows the 0. Add --format json to see the details. This doesn't work for 1.8.3 and earlier, which were built on a Mac and uploaded by hand.

Build it yourself

You can skip the download and build Relay from the source. These steps start from a clean clone of a release tag:

git clone --branch v1.9.0 --depth 1 https://github.com/aahannp/relay
cd relay
npm ci           # exact versions from package-lock.json
npm run app      # run it from source
npm run dist     # or build dist/Relay-1.9.0-arm64.dmg

You need a Mac with Apple silicon, git, Node.js with npm (we used Node 25.5.0 and npm 11.8.0), and the Xcode Command Line Tools (xcode-select --install) for swiftc, which builds the voice helper. npm run dist took under 3 minutes here.

The DMG isn't reproducible bit for bit. Our build of 1.8.2 had a different checksum from the release DMG, because a disk image records when it was made. So you can't check a download by building your own DMG and comparing checksums.

What you can compare is what's inside. The app's code, app.asar, should come out the same SHA-256 as the release's (for 1.8.2, 77906f63…20c4):

shasum -a 256 "dist/mac-arm64/Relay.app/Contents/Resources/app.asar" "/Applications/Relay.app/Contents/Resources/app.asar"

On our Mac the whole app matched: all 298 files in our dist/mac-arm64/Relay.app were identical to the released app's, signatures included. That needs the same tools as the release build, though. A different Xcode compiles the voice helper differently, and a different version of macOS's codesign can sign differently. To compare whole apps, run this in each one and compare the two results:

cd "/Applications/Relay.app" && find . -type f -print0 | sort -z | xargs -0 shasum -a 256 | shasum -a 256

What the Terminal installer does

The one-line installer on the home page, curl -fsSL https://aahannp.github.io/relay/install.sh | bash, is a short script you can read first. It:

  1. stops unless it's on macOS with Apple silicon;
  2. asks GitHub's API for the latest release and finds its .dmg;
  3. downloads it with curl into a temporary folder and mounts it read-only;
  4. stops unless that app is closed, then replaces /Applications/Relay.app with the copy from the DMG and opens it (for a 1.8.x DMG, the app is still Agent Desk.app);
  5. if you're moving to Relay and Agent Desk.app is still in Applications, says you can delete it (it never deletes it itself);
  6. unmounts the DMG and deletes the temporary folder.

There's no macOS warning because files curl downloads aren't marked as quarantined, so Gatekeeper doesn't check them on first launch. That also means macOS checks nothing, and the script doesn't check the SHA-256 either. If you want those checks, download the DMG and follow steps 1 to 3.

Something doesn't match?

Please open an issue with the version, the command you ran and its output.