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.
Latest release: ·
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
- Signature=adhoc means the app is signed, but not with a certificate from Apple. An ad-hoc signature fixes the app's contents (change one byte and the signature breaks), but it says nothing about who signed it. That's why there's no Team ID, and why macOS warns on first launch.
- runtime means it uses the hardened runtime, which stops other code being injected into it.
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:
| Part | Result | Why |
|---|---|---|
| electron/, lib/, public/, shell/, server.mjs | Identical, byte for byte | Copied as they are. |
| package.json | Shorter | electron-builder removes the scripts, devDependencies and build sections. Name, version and dependencies are the same. |
| node_modules | Same files, fewer of them | electron-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-helper | Files differ, machine code identical | They'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:
- stops unless it's on macOS with Apple silicon;
- asks GitHub's API for the latest release and finds its .dmg;
- downloads it with curl into a temporary folder and mounts it read-only;
- stops unless that app is closed, then replaces
/Applications/Relay.appwith the copy from the DMG and opens it (for a 1.8.x DMG, the app is stillAgent Desk.app); - if you're moving to Relay and
Agent Desk.appis still in Applications, says you can delete it (it never deletes it itself); - 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.