Skip to content

fix: route Nightly Google OAuth callbacks correctly (#5537) #351

fix: route Nightly Google OAuth callbacks correctly (#5537)

fix: route Nightly Google OAuth callbacks correctly (#5537) #351

name: Desktop App Release
on:
push:
branches: [main]
paths:
- ".github/workflows/desktop-release.yml"
- "packages/desktop-app/**"
- "packages/shared-app-config/**"
workflow_dispatch:
inputs:
channel:
description: "Release channel"
required: true
default: production
type: choice
options:
- production
- nightly
version:
description: "Stable version to release (e.g. 0.2.0). Required for production; Nightly may use the package.json version."
required: false
type: string
source_ref:
description: "Exact source commit to build for a coordinated release"
required: false
type: string
permissions:
contents: write
concurrency:
group: desktop-release-${{ github.event_name == 'push' && 'nightly' || inputs.channel || 'production' }}
cancel-in-progress: false
env:
# Pushes to main are the employee-facing Nightly train. Production is only
# released deliberately through workflow_dispatch.
RELEASE_CHANNEL: ${{ github.event_name == 'push' && 'nightly' || inputs.channel || 'production' }}
AGENT_NATIVE_DESKTOP_RELEASE_CHANNEL: ${{ github.event_name == 'push' && 'nightly' || inputs.channel || 'production' }}
EP_PRE_RELEASE: ${{ (github.event_name == 'push' && 'nightly' || inputs.channel || 'production') == 'nightly' }}
ELECTRON_APP_ID: ${{ (github.event_name == 'push' && 'nightly' || inputs.channel || 'production') == 'nightly' && 'com.agentnative.desktop.nightly' || 'com.agentnative.desktop' }}
ELECTRON_PRODUCT_NAME: ${{ (github.event_name == 'push' && 'nightly' || inputs.channel || 'production') == 'nightly' && 'Agent-Native Nightly' || 'Agent-Native' }}
# Deterministic version for auto-triggered builds: base version from
# package.json + run number ensures uniqueness without committing back.
AUTO_VERSION_SUFFIX: ${{ github.run_number }}
jobs:
resolve-version:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.v.outputs.version }}
source_ref: ${{ steps.v.outputs.source_ref }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
ref: ${{ inputs.source_ref || github.sha }}
- name: Resolve version
id: v
shell: bash
env:
RELEASE_VERSION_INPUT: ${{ inputs.version }}
RELEASE_SOURCE_REF_INPUT: ${{ inputs.source_ref }}
run: |
if [ -n "$RELEASE_SOURCE_REF_INPUT" ] && [[ ! "$RELEASE_SOURCE_REF_INPUT" =~ ^[0-9a-fA-F]{40}$ ]]; then
echo "::error::source_ref must be a full 40-character commit SHA."
exit 1
fi
SOURCE_REF="${RELEASE_SOURCE_REF_INPUT:-$GITHUB_SHA}"
echo "source_ref=${SOURCE_REF,,}" >> "$GITHUB_OUTPUT"
if [ -n "$RELEASE_VERSION_INPUT" ]; then
if [ "$RELEASE_CHANNEL" = "nightly" ]; then
if [[ "$RELEASE_VERSION_INPUT" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "version=${RELEASE_VERSION_INPUT}-nightly.${AUTO_VERSION_SUFFIX}" >> "$GITHUB_OUTPUT"
elif [[ "$RELEASE_VERSION_INPUT" =~ ^[0-9]+\.[0-9]+\.[0-9]+-nightly\.[0-9]+$ ]]; then
echo "version=$RELEASE_VERSION_INPUT" >> "$GITHUB_OUTPUT"
else
echo "::error::Nightly versions must be semver or include the -nightly prerelease marker."
exit 1
fi
else
if [[ ! "$RELEASE_VERSION_INPUT" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "::error::Production versions must be an exact stable major.minor.patch version."
exit 1
fi
echo "version=$RELEASE_VERSION_INPUT" >> "$GITHUB_OUTPUT"
fi
else
if [ "$RELEASE_CHANNEL" = "nightly" ]; then
BASE=$(node -p "require('./packages/desktop-app/package.json').version")
# Use a prerelease tag rather than build metadata: npm version
# silently strips +metadata, which would make release assets
# collide across automatic runs.
echo "version=${BASE}-nightly.${AUTO_VERSION_SUFFIX}" >> "$GITHUB_OUTPUT"
else
echo "::error::Production releases require an explicit stable version input."
exit 1
fi
fi
prepare-release:
needs: resolve-version
runs-on: ubuntu-latest
steps:
- name: Create draft release before parallel uploads
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: bash
run: |
TAG="v${{ needs.resolve-version.outputs.version }}"
get_tag_sha() {
local ref_json object_type object_sha
if ! ref_json=$(gh api "repos/${GITHUB_REPOSITORY}/git/ref/tags/$1"); then
return 1
fi
object_type=$(printf '%s' "$ref_json" | jq -r '.object.type')
object_sha=$(printf '%s' "$ref_json" | jq -r '.object.sha')
if [ "$object_type" = "tag" ]; then
gh api "repos/${GITHUB_REPOSITORY}/git/tags/$object_sha" --jq '.object.sha'
else
printf '%s\n' "$object_sha"
fi
}
RELEASE_ERROR=$(mktemp)
if RELEASE_JSON=$(gh api "repos/${GITHUB_REPOSITORY}/releases/tags/${TAG}" 2>"$RELEASE_ERROR"); then
IS_DRAFT=$(printf '%s' "$RELEASE_JSON" | jq -r '.draft')
if [ "$IS_DRAFT" != "true" ]; then
echo "::error::Release $TAG is already published. Choose a new version instead of rebuilding it."
rm -f "$RELEASE_ERROR"
exit 1
fi
EXISTING_TAG_SHA=$(get_tag_sha "$TAG")
if [ "$EXISTING_TAG_SHA" != "${{ needs.resolve-version.outputs.source_ref }}" ]; then
echo "::error::Existing release $TAG points to $EXISTING_TAG_SHA, expected ${{ needs.resolve-version.outputs.source_ref }}."
rm -f "$RELEASE_ERROR"
exit 1
fi
echo "Draft release $TAG already points to the coordinated source; build jobs will upload to it."
elif grep -q 'HTTP 404' "$RELEASE_ERROR"; then
TAG_ERROR=$(mktemp)
if TAG_SHA=$(get_tag_sha "$TAG" 2>"$TAG_ERROR"); then
if [ "$TAG_SHA" != "${{ needs.resolve-version.outputs.source_ref }}" ]; then
echo "::error::Existing tag $TAG points to $TAG_SHA, expected ${{ needs.resolve-version.outputs.source_ref }}."
rm -f "$RELEASE_ERROR" "$TAG_ERROR"
exit 1
fi
echo "Existing tag $TAG already points to the coordinated source; creating its release."
elif grep -q 'HTTP 404' "$TAG_ERROR"; then
:
else
cat "$TAG_ERROR"
rm -f "$RELEASE_ERROR" "$TAG_ERROR"
exit 1
fi
rm -f "$TAG_ERROR"
if [ "$RELEASE_CHANNEL" = "nightly" ]; then
gh release create "$TAG" \
--draft \
--prerelease \
--latest=false \
--target "${{ needs.resolve-version.outputs.source_ref }}" \
--title "Agent-Native Nightly v${{ needs.resolve-version.outputs.version }}" \
--notes "Auto-updating Nightly build of the Agent-Native desktop app." \
-R "${{ github.repository }}"
else
gh release create "$TAG" \
--draft \
--latest=false \
--target "${{ needs.resolve-version.outputs.source_ref }}" \
--title "Agent-Native v${{ needs.resolve-version.outputs.version }}" \
--notes "Stable release of the Agent-Native desktop app." \
-R "${{ github.repository }}"
fi
else
cat "$RELEASE_ERROR"
rm -f "$RELEASE_ERROR"
exit 1
fi
rm -f "$RELEASE_ERROR"
build-mac:
needs: [resolve-version, prepare-release]
runs-on: macos-latest
env:
NODE_OPTIONS: --max-old-space-size=8192
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
ref: ${{ needs.resolve-version.outputs.source_ref }}
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 22
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Configure Nightly channel
if: env.RELEASE_CHANNEL == 'nightly'
working-directory: packages/desktop-app
shell: bash
run: |
node <<'NODE'
const fs = require("node:fs");
const builderPath = "electron-builder.yml";
let builder = fs.readFileSync(builderPath, "utf8");
let nightlySchemeConfigured = false;
builder = builder.replace(
/^([ \t]*- )(agentnative(?:-nightly)?)([ \t]*\r?\n)$/gm,
(_match, prefix, _scheme, suffix) => {
if (nightlySchemeConfigured) return "";
nightlySchemeConfigured = true;
return `${prefix}agentnative-nightly${suffix}`;
},
);
builder = builder.replace(
/^([ \t]*- name: )Agent-Native([ \t]*\r?\n)/m,
"$1Agent-Native Nightly$2",
);
if (!nightlySchemeConfigured) {
throw new Error("Could not configure the Nightly deep-link scheme.");
}
fs.writeFileSync(builderPath, builder);
const updateConfigPath = "desktop-update-config.yml";
let updateConfig = fs.readFileSync(updateConfigPath, "utf8");
updateConfig = updateConfig.replace(
'updaterCacheDirName: "@agent-nativedesktop-app-updater"',
'updaterCacheDirName: "@agent-nativedesktop-app-updater-nightly"',
);
if (!updateConfig.includes("@agent-nativedesktop-app-updater-nightly")) {
throw new Error("Could not configure the Nightly updater cache.");
}
fs.writeFileSync(updateConfigPath, updateConfig);
NODE
- name: Set version
working-directory: packages/desktop-app
run: npm version "${{ needs.resolve-version.outputs.version }}" --no-git-tag-version --allow-same-version
- name: Import signing certificate
env:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
run: |
echo "$APPLE_CERTIFICATE" | base64 --decode > certificate.p12
security create-keychain -p "" build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p "" build.keychain
security import certificate.p12 -k build.keychain -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "" build.keychain
rm certificate.p12
- name: Build and publish
working-directory: packages/desktop-app
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: W3PMF2T3MW
SENTRY_DESKTOP_DSN: ${{ secrets.SENTRY_DESKTOP_DSN || secrets.SENTRY_ELECTRON_DSN || secrets.SENTRY_CLIENT_DSN || secrets.SENTRY_DSN }}
SENTRY_DESKTOP_CLIENT_KEY: ${{ secrets.SENTRY_DESKTOP_CLIENT_KEY || secrets.SENTRY_CLIENT_KEY }}
SENTRY_DESKTOP_PROJECT_ID: ${{ secrets.SENTRY_DESKTOP_PROJECT_ID || secrets.SENTRY_PROJECT_ID }}
SENTRY_DESKTOP_INGEST_HOST: ${{ secrets.SENTRY_DESKTOP_INGEST_HOST || secrets.SENTRY_INGEST_HOST }}
SENTRY_DESKTOP_ENVIRONMENT: ${{ env.RELEASE_CHANNEL }}
run: |
pnpm build:mac-assets && pnpm build
npx electron-builder --mac --config --publish always \
"-c.appId=$ELECTRON_APP_ID" \
"-c.productName=$ELECTRON_PRODUCT_NAME"
build-windows:
needs: [resolve-version, prepare-release]
runs-on: windows-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
ref: ${{ needs.resolve-version.outputs.source_ref }}
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 22
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Configure Nightly channel
if: env.RELEASE_CHANNEL == 'nightly'
working-directory: packages/desktop-app
shell: bash
run: |
node <<'NODE'
const fs = require("node:fs");
const builderPath = "electron-builder.yml";
let builder = fs.readFileSync(builderPath, "utf8");
let nightlySchemeConfigured = false;
builder = builder.replace(
/^([ \t]*- )(agentnative(?:-nightly)?)([ \t]*\r?\n)$/gm,
(_match, prefix, _scheme, suffix) => {
if (nightlySchemeConfigured) return "";
nightlySchemeConfigured = true;
return `${prefix}agentnative-nightly${suffix}`;
},
);
builder = builder.replace(
/^([ \t]*- name: )Agent-Native([ \t]*\r?\n)/m,
"$1Agent-Native Nightly$2",
);
if (!nightlySchemeConfigured) {
throw new Error("Could not configure the Nightly deep-link scheme.");
}
fs.writeFileSync(builderPath, builder);
const updateConfigPath = "desktop-update-config.yml";
let updateConfig = fs.readFileSync(updateConfigPath, "utf8");
updateConfig = updateConfig.replace(
'updaterCacheDirName: "@agent-nativedesktop-app-updater"',
'updaterCacheDirName: "@agent-nativedesktop-app-updater-nightly"',
);
if (!updateConfig.includes("@agent-nativedesktop-app-updater-nightly")) {
throw new Error("Could not configure the Nightly updater cache.");
}
fs.writeFileSync(updateConfigPath, updateConfig);
NODE
- name: Set version
working-directory: packages/desktop-app
run: npm version "${{ needs.resolve-version.outputs.version }}" --no-git-tag-version --allow-same-version
- name: Build and publish
working-directory: packages/desktop-app
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SENTRY_DESKTOP_DSN: ${{ secrets.SENTRY_DESKTOP_DSN || secrets.SENTRY_ELECTRON_DSN || secrets.SENTRY_CLIENT_DSN || secrets.SENTRY_DSN }}
SENTRY_DESKTOP_CLIENT_KEY: ${{ secrets.SENTRY_DESKTOP_CLIENT_KEY || secrets.SENTRY_CLIENT_KEY }}
SENTRY_DESKTOP_PROJECT_ID: ${{ secrets.SENTRY_DESKTOP_PROJECT_ID || secrets.SENTRY_PROJECT_ID }}
SENTRY_DESKTOP_INGEST_HOST: ${{ secrets.SENTRY_DESKTOP_INGEST_HOST || secrets.SENTRY_INGEST_HOST }}
SENTRY_DESKTOP_ENVIRONMENT: ${{ env.RELEASE_CHANNEL }}
run: |
pnpm build:chrome-extension && pnpm build
npx electron-builder --win --config --publish always \
"-c.appId=$ELECTRON_APP_ID" \
"-c.productName=$ELECTRON_PRODUCT_NAME"
build-linux:
needs: [resolve-version, prepare-release]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
ref: ${{ needs.resolve-version.outputs.source_ref }}
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 22
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Configure Nightly channel
if: env.RELEASE_CHANNEL == 'nightly'
working-directory: packages/desktop-app
shell: bash
run: |
node <<'NODE'
const fs = require("node:fs");
const builderPath = "electron-builder.yml";
let builder = fs.readFileSync(builderPath, "utf8");
let nightlySchemeConfigured = false;
builder = builder.replace(
/^([ \t]*- )(agentnative(?:-nightly)?)([ \t]*\r?\n)$/gm,
(_match, prefix, _scheme, suffix) => {
if (nightlySchemeConfigured) return "";
nightlySchemeConfigured = true;
return `${prefix}agentnative-nightly${suffix}`;
},
);
builder = builder.replace(
/^([ \t]*- name: )Agent-Native([ \t]*\r?\n)/m,
"$1Agent-Native Nightly$2",
);
if (!nightlySchemeConfigured) {
throw new Error("Could not configure the Nightly deep-link scheme.");
}
fs.writeFileSync(builderPath, builder);
const updateConfigPath = "desktop-update-config.yml";
let updateConfig = fs.readFileSync(updateConfigPath, "utf8");
updateConfig = updateConfig.replace(
'updaterCacheDirName: "@agent-nativedesktop-app-updater"',
'updaterCacheDirName: "@agent-nativedesktop-app-updater-nightly"',
);
if (!updateConfig.includes("@agent-nativedesktop-app-updater-nightly")) {
throw new Error("Could not configure the Nightly updater cache.");
}
fs.writeFileSync(updateConfigPath, updateConfig);
NODE
- name: Set version
working-directory: packages/desktop-app
run: npm version "${{ needs.resolve-version.outputs.version }}" --no-git-tag-version --allow-same-version
- name: Build and publish
working-directory: packages/desktop-app
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SENTRY_DESKTOP_DSN: ${{ secrets.SENTRY_DESKTOP_DSN || secrets.SENTRY_ELECTRON_DSN || secrets.SENTRY_CLIENT_DSN || secrets.SENTRY_DSN }}
SENTRY_DESKTOP_CLIENT_KEY: ${{ secrets.SENTRY_DESKTOP_CLIENT_KEY || secrets.SENTRY_CLIENT_KEY }}
SENTRY_DESKTOP_PROJECT_ID: ${{ secrets.SENTRY_DESKTOP_PROJECT_ID || secrets.SENTRY_PROJECT_ID }}
SENTRY_DESKTOP_INGEST_HOST: ${{ secrets.SENTRY_DESKTOP_INGEST_HOST || secrets.SENTRY_INGEST_HOST }}
SENTRY_DESKTOP_ENVIRONMENT: ${{ env.RELEASE_CHANNEL }}
run: |
pnpm build:chrome-extension && pnpm build
LINUX_EXECUTABLE_NAME="agent-native"
if [ "$RELEASE_CHANNEL" = "nightly" ]; then
LINUX_EXECUTABLE_NAME="agent-native-nightly"
fi
npx electron-builder --linux --config --publish always \
"-c.linux.executableName=$LINUX_EXECUTABLE_NAME" \
"-c.appId=$ELECTRON_APP_ID" \
"-c.productName=$ELECTRON_PRODUCT_NAME"
publish-release:
needs: [resolve-version, build-mac, build-windows, build-linux]
runs-on: ubuntu-latest
steps:
- name: Publish draft release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: bash
run: |
TAG="v${{ needs.resolve-version.outputs.version }}"
if [ "$RELEASE_CHANNEL" = "nightly" ]; then
gh release edit "$TAG" \
--draft=false \
--prerelease \
--latest=false \
--title "Agent-Native Nightly v${{ needs.resolve-version.outputs.version }}" \
-R "${{ github.repository }}"
else
gh release edit "$TAG" \
--draft=false \
--latest \
--title "Agent-Native v${{ needs.resolve-version.outputs.version }}" \
-R "${{ github.repository }}"
fi