chore: consolidate and harden release workflows (#10447)

This commit is contained in:
Lifei Zhou
2026-07-17 08:19:31 +10:00
committed by GitHub
parent 8419e0551c
commit af9f3d2dff
23 changed files with 798 additions and 1976 deletions
+191
View File
@@ -0,0 +1,191 @@
on:
workflow_dispatch:
workflow_call:
inputs:
version:
required: false
default: ""
type: string
ref:
type: string
required: false
default: ""
name: "Build CLI for Linux"
permissions:
contents: read
jobs:
build-cli-linux:
name: Build CLI
runs-on: ${{ matrix.build-on }}
container: ${{ matrix.container }}
strategy:
fail-fast: false
matrix:
include:
- architecture: x86_64
target-suffix: unknown-linux-gnu
build-on: ubuntu-22.04
# Pinned by digest for reproducible builds; bump explicitly when newer manylinux_2_28 images ship.
container: quay.io/pypa/manylinux_2_28_x86_64@sha256:441c35fdc6ee809ff9260894f8468ab4fea8c15dc880f8700a3f81b7922c1cda
variant: standard
- architecture: aarch64
target-suffix: unknown-linux-gnu
build-on: ubuntu-22.04-arm
# Pinned by digest for reproducible builds; bump explicitly when newer manylinux_2_28 images ship.
container: quay.io/pypa/manylinux_2_28_aarch64@sha256:8b5f2b4e8c072ae5aefeb659f22c03e1ff46e6a82f154b6c904b106c87e65ff7
variant: standard
- architecture: x86_64
target-suffix: unknown-linux-musl
build-on: ubuntu-22.04
variant: musl
- architecture: aarch64
target-suffix: unknown-linux-musl
build-on: ubuntu-22.04-arm
variant: musl
- architecture: x86_64
target-suffix: unknown-linux-gnu
build-on: ubuntu-24.04
variant: vulkan
- architecture: aarch64
target-suffix: unknown-linux-gnu
build-on: ubuntu-24.04-arm
variant: vulkan
steps:
- name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ inputs.ref }}
- name: Update version in Cargo.toml
if: ${{ inputs.version != '' }}
shell: bash
env:
VERSION: ${{ inputs.version }}
run: bash scripts/set-cargo-version.sh "$VERSION"
- name: Install Linux build dependencies (host runner)
if: matrix.container == ''
run: |
sudo apt-get update
sudo apt-get install -y \
build-essential \
pkg-config \
libssl-dev \
libdbus-1-dev \
libxcb1-dev
if [ "${{ matrix.variant }}" = "vulkan" ]; then
sudo apt-get install -y \
libvulkan-dev \
libvulkan1 \
glslc
fi
if [ "${{ matrix.variant }}" = "musl" ]; then
sudo apt-get install -y musl-tools
fi
- name: Install Linux build dependencies (manylinux container)
if: matrix.container != ''
run: |
# perl-core provides FindBin, File::Compare, etc. that openssl-sys's
# vendored openssl build needs; in AlmaLinux 8 these aren't standalone packages.
# clang provides libclang.so for bindgen (used by llama-cpp-sys-2).
# Defensive: avoid actions/checkout falling back to a tarball download if base image changes.
dnf install -y --setopt=install_weak_deps=False \
openssl-devel \
dbus-devel \
libxcb-devel \
cmake \
perl-core \
clang \
git \
tar
- name: Cache Cargo artifacts
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
key: ${{ matrix.architecture }}-${{ matrix.target-suffix }}-${{ matrix.build-on }}-${{ matrix.container || 'native' }}
- name: Build CLI (host runner)
if: matrix.container == ''
env:
RUST_LOG: debug
RUST_BACKTRACE: 1
run: |
source ./bin/activate-hermit
export TARGET="${{ matrix.architecture }}-${{ matrix.target-suffix }}"
rustup target add "${TARGET}"
echo "Building for target: ${TARGET}"
echo "Rust toolchain info:"
rustup show
FEATURE_ARGS=()
if [ "${{ matrix.variant }}" = "vulkan" ]; then
FEATURE_ARGS=(--features vulkan)
fi
if [ "${{ matrix.variant }}" = "musl" ]; then
cargo build --release --target ${TARGET} -p goose-cli --bin goose \
--no-default-features \
--features portable-default
else
cargo build --release --target ${TARGET} -p goose-cli --bin goose "${FEATURE_ARGS[@]}"
fi
- name: Build CLI (manylinux container)
if: matrix.container != ''
env:
RUST_BACKTRACE: 1
run: |
# Hermit's tool cache is host-runner-scoped; inside the container we
# bootstrap rustup directly and let rust-toolchain.toml pin the channel.
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
| sh -s -- -y --default-toolchain none --profile minimal --no-modify-path
export PATH="$HOME/.cargo/bin:$PATH"
TARGET="${{ matrix.architecture }}-${{ matrix.target-suffix }}"
RUST_CHANNEL=$(grep '^channel' rust-toolchain.toml | cut -d'"' -f2)
if [ -z "$RUST_CHANNEL" ]; then
echo "Could not parse channel from rust-toolchain.toml" >&2
exit 1
fi
rustup toolchain install "$RUST_CHANNEL" --profile minimal \
--component rustc,cargo --target "$TARGET"
rustup show
cargo build --release --target "$TARGET" -p goose-cli --bin goose
- name: Package CLI
run: |
# Hermit isn't installed in the manylinux container; tar is all this step needs.
if [ "${{ matrix.container }}" = '' ]; then
source ./bin/activate-hermit
fi
export TARGET="${{ matrix.architecture }}-${{ matrix.target-suffix }}"
export VARIANT_SUFFIX=""
if [ "${{ matrix.variant }}" = "vulkan" ]; then
VARIANT_SUFFIX="-vulkan"
fi
# Create a directory for the package contents
mkdir -p "target/${TARGET}/release/goose-package"
# Copy the goose binary
cp "target/${TARGET}/release/goose" "target/${TARGET}/release/goose-package/"
cd "target/${TARGET}/release"
tar -cjf "goose-${TARGET}${VARIANT_SUFFIX}.tar.bz2" -C goose-package .
tar -czf "goose-${TARGET}${VARIANT_SUFFIX}.tar.gz" -C goose-package .
echo "ARTIFACT_BZ2=target/${TARGET}/release/goose-${TARGET}${VARIANT_SUFFIX}.tar.bz2" >> $GITHUB_ENV
echo "ARTIFACT_GZ=target/${TARGET}/release/goose-${TARGET}${VARIANT_SUFFIX}.tar.gz" >> $GITHUB_ENV
- name: Upload CLI artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: goose-${{ matrix.architecture }}-${{ matrix.target-suffix }}${{ matrix.variant != 'standard' && matrix.variant != 'musl' && format('-{0}', matrix.variant) || '' }}
path: |
${{ env.ARTIFACT_BZ2 }}
${{ env.ARTIFACT_GZ }}
+43 -283
View File
@@ -1,14 +1,13 @@
# This is a **reuseable** workflow that builds the CLI for multiple platforms.
# It doesn't get triggered on its own. It gets used in multiple workflows:
# - release.yml
# - canary.yml
# This is a reusable workflow that builds the CLI for multiple platforms.
# It doesn't get triggered on its own. It gets used by:
# - publish-npm.yml
#
# Platform Build Strategy:
# - Linux standard (x86_64 + aarch64): Builds inside manylinux_2_28 container for glibc 2.28+ compat
# - Linux Vulkan: Uses native Ubuntu 24.04 runners for newer Vulkan headers/tooling
# - Linux musl: Uses native Ubuntu 22.04 runners with reduced features for musl compatibility
# - macOS: Uses native macOS runners for each architecture
# - Windows: Uses Windows runner with native MSVC build
# - macOS: Uses bundle-macos.yml to package CLI artifacts without Desktop
# - Windows: Uses bundle-windows.yml to package CLI artifacts without Desktop
on:
workflow_call:
inputs:
@@ -24,285 +23,46 @@ on:
name: "Reusable workflow to build CLI"
jobs:
build-cli:
name: Build CLI
runs-on: ${{ matrix.build-on }}
container: ${{ matrix.container }}
env:
MACOSX_DEPLOYMENT_TARGET: "12.0"
build-cli-macos:
strategy:
fail-fast: false
matrix:
include:
- platform: linux
architecture: x86_64
target-suffix: unknown-linux-gnu
build-on: ubuntu-22.04
# Pinned by digest for reproducible builds; bump explicitly when newer manylinux_2_28 images ship.
container: quay.io/pypa/manylinux_2_28_x86_64@sha256:441c35fdc6ee809ff9260894f8468ab4fea8c15dc880f8700a3f81b7922c1cda
variant: standard
- platform: linux
architecture: aarch64
target-suffix: unknown-linux-gnu
build-on: ubuntu-22.04-arm
# Pinned by digest for reproducible builds; bump explicitly when newer manylinux_2_28 images ship.
container: quay.io/pypa/manylinux_2_28_aarch64@sha256:8b5f2b4e8c072ae5aefeb659f22c03e1ff46e6a82f154b6c904b106c87e65ff7
variant: standard
- platform: linux
architecture: x86_64
target-suffix: unknown-linux-musl
build-on: ubuntu-22.04
variant: musl
- platform: linux
architecture: aarch64
target-suffix: unknown-linux-musl
build-on: ubuntu-22.04-arm
variant: musl
- platform: linux
architecture: x86_64
target-suffix: unknown-linux-gnu
build-on: ubuntu-24.04
variant: vulkan
- platform: linux
architecture: aarch64
target-suffix: unknown-linux-gnu
build-on: ubuntu-24.04-arm
variant: vulkan
- platform: macos
architecture: x86_64
target-suffix: apple-darwin
build-on: macos-15-intel
variant: standard
- platform: macos
architecture: aarch64
target-suffix: apple-darwin
build-on: macos-latest
variant: standard
- platform: windows
architecture: x86_64
target-suffix: pc-windows-msvc
build-on: windows-latest
variant: standard
- platform: windows
architecture: x86_64
target-suffix: pc-windows-msvc
build-on: windows-2022
variant: cuda
target:
- x86_64-apple-darwin
- aarch64-apple-darwin
uses: ./.github/workflows/bundle-macos.yml
permissions:
contents: read
with:
version: ${{ inputs.version }}
ref: ${{ inputs.ref }}
target: ${{ matrix.target }}
signing: false
package_cli: true
package_desktop: false
steps:
- name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ inputs.ref }}
build-cli-windows:
strategy:
fail-fast: false
matrix:
windows_variant:
- standard
- cuda
uses: ./.github/workflows/bundle-windows.yml
permissions:
contents: read
with:
version: ${{ inputs.version }}
ref: ${{ inputs.ref }}
signing: false
package_cli: true
package_desktop: false
windows_variant: ${{ matrix.windows_variant }}
- name: Update version in Cargo.toml
if: ${{ inputs.version != '' }}
shell: bash
run: |
sed -i.bak 's/^version = ".*"/version = "'${{ inputs.version }}'"/' Cargo.toml
rm -f Cargo.toml.bak
- name: Install Linux build dependencies (host runner)
if: matrix.platform == 'linux' && matrix.container == ''
run: |
sudo apt-get update
sudo apt-get install -y \
build-essential \
pkg-config \
libssl-dev \
libdbus-1-dev \
libxcb1-dev
if [ "${{ matrix.variant }}" = "vulkan" ]; then
sudo apt-get install -y \
libvulkan-dev \
libvulkan1 \
glslc
fi
if [ "${{ matrix.variant }}" = "musl" ]; then
sudo apt-get install -y musl-tools
fi
- name: Install Linux build dependencies (manylinux container)
if: matrix.platform == 'linux' && matrix.container != ''
run: |
# perl-core provides FindBin, File::Compare, etc. that openssl-sys's
# vendored openssl build needs; in AlmaLinux 8 these aren't standalone packages.
# clang provides libclang.so for bindgen (used by llama-cpp-sys-2).
# Defensive: avoid actions/checkout falling back to a tarball download if base image changes.
dnf install -y --setopt=install_weak_deps=False \
openssl-devel \
dbus-devel \
libxcb-devel \
cmake \
perl-core \
clang \
git \
tar
- name: Cache Cargo artifacts (Linux/macOS)
if: matrix.platform != 'windows'
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
key: ${{ matrix.architecture }}-${{ matrix.target-suffix }}-${{ matrix.build-on }}-${{ matrix.container || 'native' }}-macos-deployment-target-12
- name: Cache Cargo artifacts (Windows)
if: matrix.platform == 'windows'
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
key: windows-msvc-cli-${{ matrix.variant }}
- name: Build CLI (Linux/macOS host runner)
if: matrix.platform != 'windows' && matrix.container == ''
env:
RUST_LOG: debug
RUST_BACKTRACE: 1
run: |
source ./bin/activate-hermit
export TARGET="${{ matrix.architecture }}-${{ matrix.target-suffix }}"
rustup target add "${TARGET}"
echo "Building for target: ${TARGET}"
echo "Rust toolchain info:"
rustup show
FEATURE_ARGS=()
if [ "${{ matrix.variant }}" = "vulkan" ]; then
FEATURE_ARGS=(--features vulkan)
fi
if [ "${{ matrix.variant }}" = "musl" ]; then
cargo build --release --target ${TARGET} -p goose-cli --bin goose \
--no-default-features \
--features portable-default
else
cargo build --release --target ${TARGET} -p goose-cli "${FEATURE_ARGS[@]}"
fi
- name: Build CLI (manylinux container)
if: matrix.platform == 'linux' && matrix.container != ''
env:
RUST_BACKTRACE: 1
run: |
# Hermit's tool cache is host-runner-scoped; inside the container we
# bootstrap rustup directly and let rust-toolchain.toml pin the channel.
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
| sh -s -- -y --default-toolchain none --profile minimal --no-modify-path
export PATH="$HOME/.cargo/bin:$PATH"
TARGET="${{ matrix.architecture }}-${{ matrix.target-suffix }}"
RUST_CHANNEL=$(grep '^channel' rust-toolchain.toml | cut -d'"' -f2)
if [ -z "$RUST_CHANNEL" ]; then
echo "Could not parse channel from rust-toolchain.toml" >&2
exit 1
fi
rustup toolchain install "$RUST_CHANNEL" --profile minimal \
--component rustc,cargo --target "$TARGET"
rustup show
cargo build --release --target "$TARGET" -p goose-cli
- name: Setup Rust (Windows)
if: matrix.platform == 'windows'
shell: bash
run: |
rustup show
rustup target add x86_64-pc-windows-msvc
- name: Install CUDA toolkit (Windows CUDA)
if: ${{ matrix.platform == 'windows' && matrix.variant == 'cuda' }}
uses: Jimver/cuda-toolkit@v0.2.35
with:
cuda: '12.9.1'
method: 'local'
log-file-suffix: 'build-cli-windows-cuda.txt'
- name: Set up MSVC developer environment (Windows CUDA)
if: ${{ matrix.platform == 'windows' && matrix.variant == 'cuda' }}
uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0
with:
arch: amd64
- name: Verify CUDA toolchain (Windows CUDA)
if: ${{ matrix.platform == 'windows' && matrix.variant == 'cuda' }}
shell: pwsh
env:
CUDA_COMPUTE_CAP: "80"
run: |
Write-Output "CUDA_PATH=$env:CUDA_PATH"
Write-Output "CUDA_COMPUTE_CAP=$env:CUDA_COMPUTE_CAP"
where.exe cl
where.exe nvcc
nvcc -V
- name: Build CLI (Windows)
if: matrix.platform == 'windows'
shell: pwsh
env:
CUDA_COMPUTE_CAP: ${{ matrix.variant == 'cuda' && '80' || '' }}
run: |
Write-Output "Building Windows CLI executable..."
if ("${{ matrix.variant }}" -eq "cuda") {
cargo build --release --target x86_64-pc-windows-msvc -p goose-cli --features cuda
} else {
cargo build --release --target x86_64-pc-windows-msvc -p goose-cli
}
if (-not (Test-Path "./target/x86_64-pc-windows-msvc/release/goose.exe")) {
Write-Error "Windows CLI binary not found."
Get-ChildItem ./target/x86_64-pc-windows-msvc/release/ -ErrorAction SilentlyContinue
exit 1
}
Write-Output "Windows CLI binary found."
Get-Item ./target/x86_64-pc-windows-msvc/release/goose.exe
- name: Package CLI (Linux/macOS)
if: matrix.platform != 'windows'
run: |
# Hermit isn't installed in the manylinux container; tar is all this step needs.
if [ "${{ matrix.container }}" = '' ]; then
source ./bin/activate-hermit
fi
export TARGET="${{ matrix.architecture }}-${{ matrix.target-suffix }}"
export VARIANT_SUFFIX=""
if [ "${{ matrix.variant }}" = "vulkan" ]; then
VARIANT_SUFFIX="-vulkan"
fi
# Create a directory for the package contents
mkdir -p "target/${TARGET}/release/goose-package"
# Copy the goose binary
cp "target/${TARGET}/release/goose" "target/${TARGET}/release/goose-package/"
cd "target/${TARGET}/release"
tar -cjf "goose-${TARGET}${VARIANT_SUFFIX}.tar.bz2" -C goose-package .
tar -czf "goose-${TARGET}${VARIANT_SUFFIX}.tar.gz" -C goose-package .
echo "ARTIFACT_BZ2=target/${TARGET}/release/goose-${TARGET}${VARIANT_SUFFIX}.tar.bz2" >> $GITHUB_ENV
echo "ARTIFACT_GZ=target/${TARGET}/release/goose-${TARGET}${VARIANT_SUFFIX}.tar.gz" >> $GITHUB_ENV
- name: Package CLI (Windows)
if: matrix.platform == 'windows'
shell: bash
run: |
export TARGET="${{ matrix.architecture }}-${{ matrix.target-suffix }}"
export VARIANT_SUFFIX=""
if [ "${{ matrix.variant }}" = "cuda" ]; then
VARIANT_SUFFIX="-cuda"
fi
mkdir -p "target/${TARGET}/release/goose-package"
cp "target/${TARGET}/release/goose.exe" "target/${TARGET}/release/goose-package/"
cd "target/${TARGET}/release"
7z a -tzip "goose-${TARGET}${VARIANT_SUFFIX}.zip" goose-package/
echo "ARTIFACT_ZIP=target/${TARGET}/release/goose-${TARGET}${VARIANT_SUFFIX}.zip" >> $GITHUB_ENV
- name: Upload CLI artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: goose-${{ matrix.architecture }}-${{ matrix.target-suffix }}${{ matrix.variant != 'standard' && matrix.variant != 'musl' && format('-{0}', matrix.variant) || '' }}
path: |
${{ env.ARTIFACT_BZ2 }}
${{ env.ARTIFACT_GZ }}
${{ env.ARTIFACT_ZIP }}
build-cli-linux:
uses: ./.github/workflows/build-cli-linux.yml
permissions:
contents: read
with:
version: ${{ inputs.version }}
ref: ${{ inputs.ref }}
-203
View File
@@ -1,203 +0,0 @@
# This is a **reuseable** workflow that bundles the Desktop App for Intel macOS.
# It doesn't get triggered on its own. It gets used in multiple workflows:
# - release.yml
# - canary.yml
# - pr-comment-bundle-desktop.yml
on:
workflow_call:
inputs:
version:
description: 'Version to set for the build'
required: false
default: ""
type: string
signing:
description: 'Whether to perform signing and notarization'
required: false
default: false
type: boolean
quick_test:
description: 'Whether to perform the quick launch test'
required: false
default: true
type: boolean
ref:
type: string
required: false
default: ''
environment:
description: 'GitHub Environment containing signing secrets (e.g. "production"). Leave empty to skip.'
required: false
type: string
default: ''
name: Reusable workflow to bundle desktop app for Intel Mac
jobs:
bundle-desktop-intel:
runs-on: macos-latest
name: Bundle Desktop App on Intel macOS
environment: ${{ inputs.environment || '' }}
env:
MACOSX_DEPLOYMENT_TARGET: "12.0"
permissions:
id-token: write
contents: read
steps:
# Check initial disk space
- name: Check initial disk space
run: df -h
- name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
# Only pass ref if it's explicitly set, otherwise let checkout action use its default behavior
ref: ${{ inputs.ref != '' && inputs.ref || '' }}
# Update versions before build
- name: Update versions
if: ${{ inputs.version != '' }}
run: |
# Update version in Cargo.toml
sed -i.bak 's/^version = ".*"/version = "'${{ inputs.version }}'"/' Cargo.toml
rm -f Cargo.toml.bak
# Update version in package.json
source ./bin/activate-hermit
cd ui/desktop
npm pkg set "version=${{ inputs.version }}"
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
key: intel-macos-deployment-target-12
- name: Build desktop backend for Intel macOS (x86_64)
run: |
source ./bin/activate-hermit
rustup target add x86_64-apple-darwin
cargo build --release -p goose-cli --bin goose --target x86_64-apple-darwin
# Post-build cleanup to free space
- name: Post-build cleanup
run: |
echo "Performing post-build cleanup..."
# Remove debug artifacts
rm -rf target/debug || true
rm -rf target/x86_64-apple-darwin/debug || true
# Keep only what's needed for the next steps
rm -rf target/x86_64-apple-darwin/release/deps || true
rm -rf target/x86_64-apple-darwin/release/build || true
rm -rf target/x86_64-apple-darwin/release/incremental || true
# Check disk space after cleanup
df -h
- name: Copy backend binary into Electron folder
run: |
mkdir -p ui/desktop/src/bin
rm -f ui/desktop/src/bin/goose
cp target/x86_64-apple-darwin/release/goose ui/desktop/src/bin/goose
chmod +x ui/desktop/src/bin/goose
ls -la ui/desktop/src/bin/
- name: Cache pnpm dependencies
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
ui/desktop/node_modules
.hermit/node/cache
key: intel-pnpm-cache-v1-${{ runner.os }}-${{ hashFiles('ui/pnpm-lock.yaml') }}
restore-keys: |
intel-pnpm-cache-v1-${{ runner.os }}-
- name: Install dependencies
run: source ../../bin/activate-hermit && pnpm install --frozen-lockfile
working-directory: ui/desktop
# Configure Electron builder for Intel architecture
- name: Configure for Intel build
run: |
# Set the architecture to x64 for Intel Mac build
jq '.build.mac.target[0].arch = "x64"' package.json > package.json.tmp && mv package.json.tmp package.json
working-directory: ui/desktop
- name: Import Apple signing certificate
if: ${{ inputs.signing }}
uses: ./.github/actions/apple-codesign
with:
certificate-base64: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
certificate-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
# Check disk space before bundling
- name: Check disk space before bundling
run: df -h
- name: Build App
env:
APPLE_ID: ${{ inputs.signing && secrets.APPLE_ID || '' }}
APPLE_ID_PASSWORD: ${{ inputs.signing && secrets.APPLE_ID_PASSWORD || '' }}
APPLE_TEAM_ID: ${{ inputs.signing && secrets.APPLE_TEAM_ID || '' }}
run: |
source ../../bin/activate-hermit
attempt=0
max_attempts=2
until [ $attempt -ge $max_attempts ]; do
pnpm run bundle:intel && break
attempt=$((attempt + 1))
echo "Attempt $attempt failed. Retrying..."
sleep 5
done
if [ $attempt -ge $max_attempts ]; then
echo "Action failed after $max_attempts attempts."
exit 1
fi
working-directory: ui/desktop
- name: Verify macOS updater resources
run: node scripts/verify-mac-update-resources.js "out/Goose-darwin-x64/Goose.app"
working-directory: ui/desktop
- name: Clean up signing keychain
if: always()
run: |
if [ -n "$KEYCHAIN_PATH" ] && [ -f "$KEYCHAIN_PATH" ]; then
security delete-keychain "$KEYCHAIN_PATH" || true
fi
- name: Final cleanup before artifact upload
run: |
echo "Performing final cleanup..."
# Remove build artifacts that are no longer needed
rm -rf target || true
# Check disk space after cleanup
df -h
- name: Upload Desktop artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Goose-darwin-x64
path: ui/desktop/out/Goose-darwin-x64/Goose_intel_mac.zip
- name: Quick launch test (macOS)
if: ${{ inputs.quick_test }}
run: |
# Ensure no quarantine attributes (if needed)
xattr -cr "ui/desktop/out/Goose-darwin-x64/Goose.app"
echo "Opening Goose.app..."
open -g "ui/desktop/out/Goose-darwin-x64/Goose.app"
# Give the app a few seconds to start and write logs
sleep 5
# Check if it's running
if pgrep -f "Goose.app/Contents/MacOS/Goose" > /dev/null; then
echo "App appears to be running."
else
echo "App did not stay open. Possible crash or startup error."
exit 1
fi
# Kill the app to clean up
pkill -f "Goose.app/Contents/MacOS/Goose"
+8 -12
View File
@@ -5,11 +5,6 @@
# - pr-comment-bundle-desktop.yml (when added)
on:
workflow_dispatch:
inputs:
branch:
description: 'Branch name to bundle app from'
required: true
type: string
workflow_call:
inputs:
version:
@@ -24,6 +19,9 @@ on:
name: "Bundle Desktop (Linux)"
permissions:
contents: read
jobs:
build-desktop-linux:
name: Build Desktop (Linux, ${{ matrix.variant }})
@@ -41,19 +39,17 @@ jobs:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ inputs.ref || inputs.branch }}
ref: ${{ inputs.ref }}
- name: Update versions
if: ${{ inputs.version != '' }}
env:
VERSION: ${{ inputs.version }}
run: |
# Update version in Cargo.toml
sed -i.bak 's/^version = ".*"/version = "'${{ inputs.version }}'"/' Cargo.toml
rm -f Cargo.toml.bak
# Update version in package.json
bash scripts/set-cargo-version.sh "$VERSION"
source ./bin/activate-hermit
cd ui/desktop
npm pkg set "version=${{ inputs.version }}"
npm pkg set "version=${VERSION}"
- name: Debug workflow info
env:
@@ -1,28 +0,0 @@
name: Manual Desktop Bundle (Unsigned)
on:
workflow_dispatch:
inputs:
branch:
description: 'Branch name to bundle app from'
required: true
type: string
jobs:
bundle-desktop-unsigned:
uses: ./.github/workflows/bundle-desktop.yml
permissions:
id-token: write
contents: read
with:
signing: false
ref: ${{ inputs.branch }}
bundle-desktop-intel-unsigned:
uses: ./.github/workflows/bundle-desktop-intel.yml
permissions:
id-token: write
contents: read
with:
signing: false
ref: ${{ inputs.branch }}
-238
View File
@@ -1,238 +0,0 @@
# This is a **reuseable** workflow that bundles the Desktop App for macOS.
# It doesn't get triggered on its own. It gets used in multiple workflows:
# - release.yml
# - canary.yml
# - pr-comment-bundle-desktop.yml
# - bundle-desktop-manual.yml
on:
workflow_call:
inputs:
version:
description: 'Version to set for the build'
required: false
default: ""
type: string
signing:
description: 'Whether to perform signing and notarization'
required: false
default: false
type: boolean
quick_test:
description: 'Whether to perform the quick launch test'
required: false
default: true
type: boolean
ref:
description: 'Git ref to checkout (branch, tag, or SHA). Defaults to main branch if not specified.'
required: false
type: string
default: ''
environment:
description: 'GitHub Environment containing signing secrets (e.g. "signing"). Leave empty to skip.'
required: false
type: string
default: ''
name: Reusable workflow to bundle desktop app
jobs:
bundle-desktop:
runs-on: macos-latest
name: Bundle Desktop App on macOS
environment: ${{ inputs.environment || '' }}
env:
MACOSX_DEPLOYMENT_TARGET: "12.0"
permissions:
id-token: write
contents: read
outputs:
artifact-url: ${{ steps.upload-app-bundle.outputs.artifact-url }}
steps:
# Debug information about the workflow and inputs
- name: Debug workflow info
env:
WORKFLOW_NAME: ${{ github.workflow }}
WORKFLOW_REF: ${{ github.ref }}
EVENT_NAME: ${{ github.event_name }}
REPOSITORY: ${{ github.repository }}
INPUT_REF: ${{ inputs.ref }}
INPUT_VERSION: ${{ inputs.version }}
INPUT_SIGNING: ${{ inputs.signing }}
INPUT_QUICK_TEST: ${{ inputs.quick_test }}
run: |
echo "=== Workflow Information ==="
echo "Workflow: ${WORKFLOW_NAME}"
echo "Ref: ${WORKFLOW_REF}"
echo "Event: ${EVENT_NAME}"
echo "Repo: ${REPOSITORY}"
echo ""
echo "=== Input Parameters ==="
echo "Build ref: ${INPUT_REF:-<default branch>}"
echo "Version: ${INPUT_VERSION:-not set}"
echo "Signing: ${INPUT_SIGNING:-false}"
echo "Quick test: ${INPUT_QUICK_TEST:-true}"
# Check initial disk space
- name: Check initial disk space
run: df -h
- name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
# Only pass ref if it's explicitly set, otherwise let checkout action use its default behavior
ref: ${{ inputs.ref != '' && inputs.ref || '' }}
- name: Debug git status
run: |
echo "=== Git Status ==="
git status
echo ""
echo "=== Current Commit ==="
git rev-parse HEAD
git rev-parse --abbrev-ref HEAD
echo ""
echo "=== Recent Commits ==="
git log --oneline -n 5
echo ""
echo "=== Remote Branches ==="
git branch -r
# Update versions before build
- name: Update versions
if: ${{ inputs.version != '' }}
env:
VERSION: ${{ inputs.version }}
run: |
# Update version in Cargo.toml
sed -i.bak "s/^version = \".*\"/version = \"${VERSION}\"/" Cargo.toml
rm -f Cargo.toml.bak
source ./bin/activate-hermit
# Update version in package.json
cd ui/desktop
npm pkg set "version=${VERSION}"
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
key: macos-deployment-target-12
# Build the project
- name: Build desktop backend
run: |
source ./bin/activate-hermit
cargo build --release -p goose-cli --bin goose
# Post-build cleanup to free space
- name: Post-build cleanup
run: |
echo "Performing post-build cleanup..."
# Remove debug artifacts
rm -rf target/debug || true
# Keep only what's needed for the next steps
rm -rf target/release/deps || true
rm -rf target/release/build || true
rm -rf target/release/incremental || true
# Check disk space after cleanup
df -h
- name: Copy backend binary into Electron folder
run: |
mkdir -p ui/desktop/src/bin
rm -f ui/desktop/src/bin/goose
cp target/release/goose ui/desktop/src/bin/goose
chmod +x ui/desktop/src/bin/goose
ls -la ui/desktop/src/bin/
- name: Cache pnpm dependencies
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
ui/desktop/node_modules
.hermit/node/cache
key: macos-pnpm-cache-v1-${{ runner.os }}-${{ hashFiles('ui/pnpm-lock.yaml') }}
restore-keys: |
macos-pnpm-cache-v1-${{ runner.os }}-
- name: Install dependencies
run: source ../../bin/activate-hermit && pnpm install --frozen-lockfile
working-directory: ui/desktop
- name: Import Apple signing certificate
if: ${{ inputs.signing }}
uses: ./.github/actions/apple-codesign
with:
certificate-base64: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
certificate-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
# Check disk space before bundling
- name: Check disk space before bundling
run: df -h
- name: Build App
env:
APPLE_ID: ${{ inputs.signing && secrets.APPLE_ID || '' }}
APPLE_ID_PASSWORD: ${{ inputs.signing && secrets.APPLE_ID_PASSWORD || '' }}
APPLE_TEAM_ID: ${{ inputs.signing && secrets.APPLE_TEAM_ID || '' }}
run: |
source ../../bin/activate-hermit
attempt=0
max_attempts=2
until [ $attempt -ge $max_attempts ]; do
pnpm run bundle:default && break
attempt=$((attempt + 1))
echo "Attempt $attempt failed. Retrying..."
sleep 5
done
if [ $attempt -ge $max_attempts ]; then
echo "Action failed after $max_attempts attempts."
exit 1
fi
working-directory: ui/desktop
- name: Verify macOS updater resources
run: node scripts/verify-mac-update-resources.js "out/Goose-darwin-arm64/Goose.app"
working-directory: ui/desktop
- name: Clean up signing keychain
if: always()
run: |
if [ -n "$KEYCHAIN_PATH" ] && [ -f "$KEYCHAIN_PATH" ]; then
security delete-keychain "$KEYCHAIN_PATH" || true
fi
- name: Final cleanup before artifact upload
run: |
echo "Performing final cleanup..."
# Remove build artifacts that are no longer needed
rm -rf target || true
# Check disk space after cleanup
df -h
- name: Upload Desktop artifact
id: upload-app-bundle
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Goose-darwin-arm64
path: ui/desktop/out/Goose-darwin-arm64/Goose.zip
- name: Quick launch test (macOS)
if: ${{ inputs.quick_test }}
run: |
# Ensure no quarantine attributes (if needed)
xattr -cr "ui/desktop/out/Goose-darwin-arm64/Goose.app"
echo "Opening Goose.app..."
open -g "ui/desktop/out/Goose-darwin-arm64/Goose.app"
# Give the app a few seconds to start and write logs
sleep 5
# Check if it's running
if pgrep -f "Goose.app/Contents/MacOS/Goose" > /dev/null; then
echo "App appears to be running."
else
echo "App did not stay open. Possible crash or startup error."
exit 1
fi
# Kill the app to clean up
pkill -f "Goose.app/Contents/MacOS/Goose"
+326
View File
@@ -0,0 +1,326 @@
on:
workflow_dispatch:
inputs:
target:
description: 'macOS Rust target'
required: true
type: choice
default: aarch64-apple-darwin
options:
- aarch64-apple-darwin
- x86_64-apple-darwin
signing:
description: 'Whether to perform signing and notarization'
required: false
default: false
type: boolean
package_cli:
description: 'Whether to package and upload the CLI artifact'
required: false
default: false
type: boolean
package_desktop:
description: 'Whether to package and upload the Desktop artifact'
required: false
default: false
type: boolean
workflow_call:
inputs:
version:
description: 'Version to set for the build'
required: false
default: ""
type: string
target:
description: 'macOS Rust target: aarch64-apple-darwin or x86_64-apple-darwin'
required: true
type: string
signing:
description: 'Whether to perform signing and notarization'
required: false
default: false
type: boolean
package_cli:
description: 'Whether to package and upload the CLI artifact'
required: false
default: false
type: boolean
package_desktop:
description: 'Whether to package and upload the Desktop artifact'
required: false
default: false
type: boolean
ref:
description: 'Git ref to checkout (branch, tag, or SHA). Defaults to main branch if not specified.'
required: false
type: string
default: ''
name: "Bundle CLI and Desktop (macOS)"
jobs:
build-goose:
name: Build Goose (macOS)
runs-on: ${{ inputs.target == 'x86_64-apple-darwin' && 'macos-15-intel' || 'macos-latest' }}
env:
MACOSX_DEPLOYMENT_TARGET: "12.0"
permissions:
contents: read
steps:
- name: Debug workflow info
env:
WORKFLOW_NAME: ${{ github.workflow }}
WORKFLOW_REF: ${{ github.ref }}
EVENT_NAME: ${{ github.event_name }}
REPOSITORY: ${{ github.repository }}
INPUT_REF: ${{ inputs.ref }}
INPUT_VERSION: ${{ inputs.version }}
INPUT_TARGET: ${{ inputs.target }}
INPUT_SIGNING: ${{ inputs.signing }}
INPUT_PACKAGE_CLI: ${{ inputs.package_cli }}
INPUT_PACKAGE_DESKTOP: ${{ inputs.package_desktop }}
run: |
echo "=== Workflow Information ==="
echo "Workflow: ${WORKFLOW_NAME}"
echo "Ref: ${WORKFLOW_REF}"
echo "Event: ${EVENT_NAME}"
echo "Repo: ${REPOSITORY}"
echo ""
echo "=== Input Parameters ==="
echo "Build ref: ${INPUT_REF:-<default branch>}"
echo "Version: ${INPUT_VERSION:-not set}"
echo "Target: ${INPUT_TARGET}"
echo "Signing: ${INPUT_SIGNING:-false}"
echo "Package CLI: ${INPUT_PACKAGE_CLI:-false}"
echo "Package Desktop: ${INPUT_PACKAGE_DESKTOP:-false}"
- name: Check initial disk space
run: df -h
- name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ inputs.ref != '' && inputs.ref || '' }}
- name: Debug git status
run: |
echo "=== Git Status ==="
git status
echo ""
echo "=== Current Commit ==="
git rev-parse HEAD
git rev-parse --abbrev-ref HEAD
echo ""
echo "=== Recent Commits ==="
git log --oneline -n 5
echo ""
echo "=== Remote Branches ==="
git branch -r
- name: Update Cargo version
if: ${{ inputs.version != '' }}
env:
VERSION: ${{ inputs.version }}
run: bash scripts/set-cargo-version.sh "$VERSION"
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
key: ${{ inputs.target == 'x86_64-apple-darwin' && 'intel-macos-deployment-target-12' || 'macos-deployment-target-12' }}
- name: Build goose
env:
TARGET: ${{ inputs.target }}
run: |
source ./bin/activate-hermit
rustup target add "$TARGET"
cargo build --release -p goose-cli --bin goose --target "$TARGET"
- name: Prepare binary artifact
env:
TARGET: ${{ inputs.target }}
run: |
mkdir -p artifacts
cp "target/${TARGET}/release/goose" "artifacts/internal-goose-${TARGET}"
- name: Upload binary artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: internal-goose-${{ inputs.target }}
path: artifacts/internal-goose-${{ inputs.target }}
if-no-files-found: error
retention-days: 1
overwrite: true
package-cli:
name: Package CLI (macOS)
if: ${{ inputs.package_cli }}
needs: build-goose
runs-on: ubuntu-latest
permissions: {}
steps:
- name: Download binary artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: internal-goose-${{ inputs.target }}
path: package
- name: Package CLI
env:
TARGET: ${{ inputs.target }}
run: |
mv "package/internal-goose-${TARGET}" package/goose
chmod +x package/goose
mkdir -p dist
tar -cjf "dist/goose-${TARGET}.tar.bz2" -C package .
tar -czf "dist/goose-${TARGET}.tar.gz" -C package .
- name: Upload CLI artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: goose-${{ inputs.target }}
path: |
dist/goose-${{ inputs.target }}.tar.bz2
dist/goose-${{ inputs.target }}.tar.gz
if-no-files-found: error
overwrite: true
package-desktop:
name: Package Desktop (macOS)
if: ${{ inputs.package_desktop }}
needs: build-goose
runs-on: ${{ inputs.target == 'x86_64-apple-darwin' && 'macos-15-intel' || 'macos-latest' }}
environment: ${{ inputs.signing && 'signing' || null }}
env:
MACOSX_DEPLOYMENT_TARGET: "12.0"
permissions:
contents: read
outputs:
artifact-url: ${{ steps.upload-app-bundle.outputs.artifact-url }}
steps:
- name: Check initial disk space
run: df -h
- name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ inputs.ref != '' && inputs.ref || '' }}
- name: Update desktop version
if: ${{ inputs.version != '' }}
env:
VERSION: ${{ inputs.version }}
run: |
source ./bin/activate-hermit
cd ui/desktop
npm pkg set "version=${VERSION}"
- name: Download binary artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: internal-goose-${{ inputs.target }}
path: ui/desktop/src/bin
- name: Prepare desktop backend
env:
TARGET: ${{ inputs.target }}
run: |
rm -f ui/desktop/src/bin/goose
mv "ui/desktop/src/bin/internal-goose-${TARGET}" ui/desktop/src/bin/goose
chmod +x ui/desktop/src/bin/goose
ls -la ui/desktop/src/bin/
- name: Cache pnpm dependencies
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
ui/desktop/node_modules
.hermit/node/cache
key: ${{ inputs.target == 'x86_64-apple-darwin' && 'intel-pnpm-cache-v1' || 'macos-pnpm-cache-v1' }}-${{ runner.os }}-${{ hashFiles('ui/pnpm-lock.yaml') }}
restore-keys: |
${{ inputs.target == 'x86_64-apple-darwin' && 'intel-pnpm-cache-v1' || 'macos-pnpm-cache-v1' }}-${{ runner.os }}-
- name: Install dependencies
run: source ../../bin/activate-hermit && pnpm install --frozen-lockfile
working-directory: ui/desktop
- name: Configure for Intel build
if: ${{ inputs.target == 'x86_64-apple-darwin' }}
run: jq '.build.mac.target[0].arch = "x64"' package.json > package.json.tmp && mv package.json.tmp package.json
working-directory: ui/desktop
- name: Import Apple signing certificate
if: ${{ inputs.signing }}
uses: ./.github/actions/apple-codesign
with:
certificate-base64: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
certificate-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
- name: Check disk space before bundling
run: df -h
- name: Build App
env:
APPLE_ID: ${{ inputs.signing && secrets.APPLE_ID || '' }}
APPLE_ID_PASSWORD: ${{ inputs.signing && secrets.APPLE_ID_PASSWORD || '' }}
APPLE_TEAM_ID: ${{ inputs.signing && secrets.APPLE_TEAM_ID || '' }}
BUNDLE_SCRIPT: ${{ inputs.target == 'x86_64-apple-darwin' && 'bundle:intel' || 'bundle:default' }}
run: |
source ../../bin/activate-hermit
attempt=0
max_attempts=2
until [ $attempt -ge $max_attempts ]; do
pnpm run "$BUNDLE_SCRIPT" && break
attempt=$((attempt + 1))
echo "Attempt $attempt failed. Retrying..."
sleep 5
done
if [ $attempt -ge $max_attempts ]; then
echo "Action failed after $max_attempts attempts."
exit 1
fi
working-directory: ui/desktop
- name: Verify macOS updater resources
env:
APP_PATH: ${{ inputs.target == 'x86_64-apple-darwin' && 'out/Goose-darwin-x64/Goose.app' || 'out/Goose-darwin-arm64/Goose.app' }}
run: node scripts/verify-mac-update-resources.js "$APP_PATH"
working-directory: ui/desktop
- name: Clean up signing keychain
if: always()
run: |
if [ -n "$KEYCHAIN_PATH" ] && [ -f "$KEYCHAIN_PATH" ]; then
security delete-keychain "$KEYCHAIN_PATH" || true
fi
- name: Final cleanup before artifact upload
run: |
rm -f ui/desktop/src/bin/goose
df -h
- name: Upload Desktop artifact
id: upload-app-bundle
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ${{ inputs.target == 'x86_64-apple-darwin' && 'Goose-darwin-x64' || 'Goose-darwin-arm64' }}
path: ${{ inputs.target == 'x86_64-apple-darwin' && 'ui/desktop/out/Goose-darwin-x64/Goose_intel_mac.zip' || 'ui/desktop/out/Goose-darwin-arm64/Goose.zip' }}
if-no-files-found: error
overwrite: true
- name: Quick launch test (macOS)
env:
APP_PATH: ${{ inputs.target == 'x86_64-apple-darwin' && 'ui/desktop/out/Goose-darwin-x64/Goose.app' || 'ui/desktop/out/Goose-darwin-arm64/Goose.app' }}
run: |
xattr -cr "$APP_PATH"
echo "Opening Goose.app..."
open -g "$APP_PATH"
sleep 5
if pgrep -f "Goose.app/Contents/MacOS/Goose" > /dev/null; then
echo "App appears to be running."
else
echo "App did not stay open. Possible crash or startup error."
exit 1
fi
pkill -f "Goose.app/Contents/MacOS/Goose"
@@ -1,4 +1,4 @@
name: "Bundle Desktop (Windows)"
name: "Bundle CLI and Desktop (Windows)"
on:
workflow_dispatch:
@@ -8,10 +8,23 @@ on:
required: false
type: boolean
default: false
windows_variant:
description: 'Windows artifact variant to build'
package_cli:
description: 'Whether to package and upload the CLI artifact'
required: false
type: string
type: boolean
default: false
package_desktop:
description: 'Whether to package and upload the Desktop artifact'
required: false
type: boolean
default: false
windows_variant:
description: 'Windows artifact variant: standard or cuda'
required: false
type: choice
options:
- standard
- cuda
default: 'standard'
workflow_call:
inputs:
@@ -24,52 +37,46 @@ on:
required: false
type: boolean
default: false
package_cli:
description: 'Whether to package and upload the CLI artifact'
required: false
type: boolean
default: false
package_desktop:
description: 'Whether to package and upload the Desktop artifact'
required: false
type: boolean
default: false
ref:
description: 'Git ref to checkout'
required: false
type: string
default: ''
windows_variant:
description: 'Windows artifact variant to build'
description: 'Windows artifact variant: standard or cuda'
required: false
type: string
default: 'standard'
# Permissions required for OIDC authentication with Azure Trusted Signing
permissions:
id-token: write # Required to fetch the OIDC token for Azure federated credentials
contents: read # Required by actions/checkout
actions: read # May be needed for some workflows
contents: read
jobs:
build-desktop-windows:
name: Build Desktop (Windows)
build-goose-windows:
name: Build Goose (Windows)
runs-on: ${{ inputs.windows_variant == 'cuda' && 'windows-2022' || 'windows-latest' }}
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ inputs.ref != '' && inputs.ref || '' }}
- name: Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 24.10.0
- name: Install pnpm
run: npm install -g pnpm@10.30.3
- name: Cache node_modules
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
node_modules
ui/desktop/node_modules
.hermit/node/cache
key: windows-pnpm-cache-v1-${{ runner.os }}-node24-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
windows-pnpm-cache-v1-${{ runner.os }}-node24-
- name: Update Cargo version
if: ${{ inputs.version != '' }}
shell: bash
env:
VERSION: ${{ inputs.version }}
run: bash scripts/set-cargo-version.sh "$VERSION"
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
@@ -82,21 +89,21 @@ jobs:
rustup show
rustup target add x86_64-pc-windows-msvc
- name: Install CUDA toolkit (Windows CUDA)
- name: Install CUDA toolkit
if: ${{ inputs.windows_variant == 'cuda' }}
uses: Jimver/cuda-toolkit@v0.2.35
uses: Jimver/cuda-toolkit@3d45d157f327c09c04b50ee6ccdea2d9d017ec76 # v0.2.35
with:
cuda: '12.9.1'
method: 'local'
log-file-suffix: 'bundle-desktop-windows-cuda.txt'
log-file-suffix: 'bundle-windows-cuda.txt'
- name: Set up MSVC developer environment (Windows CUDA)
- name: Set up MSVC developer environment
if: ${{ inputs.windows_variant == 'cuda' }}
uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0
with:
arch: amd64
- name: Verify CUDA toolchain (Windows CUDA)
- name: Verify CUDA toolchain
if: ${{ inputs.windows_variant == 'cuda' }}
shell: pwsh
env:
@@ -115,43 +122,111 @@ jobs:
run: |
$isCuda = "${{ inputs.windows_variant }}" -eq "cuda"
Write-Output "Building Windows ACP backend"
if ($isCuda) {
cargo build --release --target x86_64-pc-windows-msvc -p goose-cli --bin goose --features cuda
} else {
cargo build --release --target x86_64-pc-windows-msvc -p goose-cli --bin goose
}
$binaryPath = "./target/x86_64-pc-windows-msvc/release/goose.exe"
# Verify build succeeded
$binaryPath = "./target/x86_64-pc-windows-msvc/release/goose.exe"
if (-not (Test-Path $binaryPath)) {
Write-Error "Windows backend binary not found: $binaryPath"
Write-Error "Windows binary not found: $binaryPath"
Get-ChildItem ./target/x86_64-pc-windows-msvc/release/ -ErrorAction SilentlyContinue
exit 1
}
Write-Output "Windows backend binary found."
Get-Item $binaryPath
- name: Upload binary artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: internal-goose-x86_64-pc-windows-msvc${{ inputs.windows_variant == 'cuda' && '-cuda' || '' }}
path: target/x86_64-pc-windows-msvc/release/goose.exe
if-no-files-found: error
retention-days: 1
overwrite: true
package-cli-windows:
name: Package CLI (Windows)
if: ${{ inputs.package_cli }}
needs: build-goose-windows
runs-on: ${{ inputs.windows_variant == 'cuda' && 'windows-2022' || 'windows-latest' }}
steps:
- name: Download binary artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: internal-goose-x86_64-pc-windows-msvc${{ inputs.windows_variant == 'cuda' && '-cuda' || '' }}
path: package
- name: Package CLI
shell: bash
env:
VARIANT_SUFFIX: ${{ inputs.windows_variant == 'cuda' && '-cuda' || '' }}
run: |
mkdir -p goose-package
cp package/goose.exe goose-package/
7z a -tzip "goose-x86_64-pc-windows-msvc${VARIANT_SUFFIX}.zip" goose-package/
- name: Upload CLI artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: goose-x86_64-pc-windows-msvc${{ inputs.windows_variant == 'cuda' && '-cuda' || '' }}
path: goose-x86_64-pc-windows-msvc${{ inputs.windows_variant == 'cuda' && '-cuda' || '' }}.zip
if-no-files-found: error
overwrite: true
build-desktop-windows:
name: Build Desktop (Windows)
if: ${{ inputs.package_desktop }}
needs: build-goose-windows
runs-on: ${{ inputs.windows_variant == 'cuda' && 'windows-2022' || 'windows-latest' }}
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ inputs.ref != '' && inputs.ref || '' }}
- name: Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 24.10.0
- name: Update desktop version
if: ${{ inputs.version != '' }}
shell: bash
env:
VERSION: ${{ inputs.version }}
run: |
cd ui/desktop
npm pkg set "version=${VERSION}"
- name: Install pnpm
run: npm install -g pnpm@10.30.3
- name: Cache node_modules
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
node_modules
ui/desktop/node_modules
.hermit/node/cache
key: windows-pnpm-cache-v1-${{ runner.os }}-node24-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
windows-pnpm-cache-v1-${{ runner.os }}-node24-
- name: Download binary artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: internal-goose-x86_64-pc-windows-msvc${{ inputs.windows_variant == 'cuda' && '-cuda' || '' }}
path: backend
- name: Prepare Windows binary
shell: bash
run: |
BACKEND_BINARY="./target/x86_64-pc-windows-msvc/release/goose.exe"
if [ ! -f "$BACKEND_BINARY" ]; then
echo "Windows backend binary not found: $BACKEND_BINARY"
exit 1
fi
echo "Cleaning destination directory..."
rm -rf ./ui/desktop/src/bin
mkdir -p ./ui/desktop/src/bin
echo "Copying Windows backend binary..."
cp -f "$BACKEND_BINARY" ./ui/desktop/src/bin/
cp -f ./backend/goose.exe ./ui/desktop/src/bin/
if [ -d "./ui/desktop/src/platform/windows/bin" ]; then
echo "Copying Windows platform files..."
for file in ./ui/desktop/src/platform/windows/bin/*.{exe,dll,cmd}; do
filename="$(basename "$file")"
if [ -f "$file" ] && [ "$filename" != "goose.exe" ]; then
@@ -160,10 +235,8 @@ jobs:
done
if [ -d "./ui/desktop/src/platform/windows/bin/goose-npm" ]; then
echo "Setting up npm environment..."
cp -r ./ui/desktop/src/platform/windows/bin/goose-npm/ ./ui/desktop/src/bin/goose-npm/
fi
echo "Windows-specific files copied successfully"
fi
- name: Force GitHub HTTPS for npm git dependencies
@@ -179,13 +252,12 @@ jobs:
ELECTRON_PLATFORM: win32
run: |
cd ui/desktop
pnpm install --frozen-lockfile
node scripts/build-main.js
node scripts/prepare-platform-binaries.js
pnpm run make --platform=win32 --arch=x64
- name: Copy exe to final out folder and prepare flat distribution
- name: Prepare flat distribution
shell: bash
run: |
cd ui/desktop
@@ -195,29 +267,31 @@ jobs:
mkdir -p ./dist-windows
cp -r ./out/Goose-win32-x64/* ./dist-windows/
echo "📋 Final flat distribution structure:"
ls -la ./dist-windows/
echo "📋 Binary files in resources/bin:"
ls -la ./dist-windows/resources/bin/
- name: Upload unsigned distribution
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: windows-unsigned${{ inputs.windows_variant == 'cuda' && '-cuda' || '' }}
name: internal-windows-unsigned${{ inputs.windows_variant == 'cuda' && '-cuda' || '' }}
path: ui/desktop/dist-windows/
if-no-files-found: error
retention-days: 1
overwrite: true
sign-desktop-windows:
name: Sign Desktop (Windows)
needs: build-desktop-windows
if: inputs.signing
if: ${{ inputs.package_desktop && inputs.signing }}
runs-on: windows-latest
environment: ${{ inputs.signing && 'signing' || null }}
permissions:
id-token: write
steps:
- name: Download unsigned distribution
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: windows-unsigned${{ inputs.windows_variant == 'cuda' && '-cuda' || '' }}
name: internal-windows-unsigned${{ inputs.windows_variant == 'cuda' && '-cuda' || '' }}
path: dist-windows
- name: Azure login
@@ -253,44 +327,40 @@ jobs:
- name: Create Windows zip package
shell: bash
run: |
ZIP_NAME="Goose-win32-x64"
if [ "${{ inputs.windows_variant }}" = "cuda" ]; then
ZIP_NAME="${ZIP_NAME}-cuda"
fi
7z a -tzip "${ZIP_NAME}.zip" dist-windows/
env:
VARIANT_SUFFIX: ${{ inputs.windows_variant == 'cuda' && '-cuda' || '' }}
run: 7z a -tzip "Goose-win32-x64${VARIANT_SUFFIX}.zip" dist-windows/
- name: Upload signed Windows build
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Goose-win32-x64${{ inputs.windows_variant == 'cuda' && '-cuda' || '' }}
path: Goose-win32-x64${{ inputs.windows_variant == 'cuda' && '-cuda' || '' }}.zip
if-no-files-found: error
overwrite: true
# When signing is disabled, package the unsigned build directly
package-desktop-windows:
name: Package Desktop (Windows)
needs: build-desktop-windows
if: ${{ !inputs.signing }}
if: ${{ inputs.package_desktop && !inputs.signing }}
runs-on: windows-latest
steps:
- name: Download unsigned distribution
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: windows-unsigned${{ inputs.windows_variant == 'cuda' && '-cuda' || '' }}
name: internal-windows-unsigned${{ inputs.windows_variant == 'cuda' && '-cuda' || '' }}
path: dist-windows
- name: Create Windows zip package
shell: bash
run: |
ZIP_NAME="Goose-win32-x64"
if [ "${{ inputs.windows_variant }}" = "cuda" ]; then
ZIP_NAME="${ZIP_NAME}-cuda"
fi
7z a -tzip "${ZIP_NAME}.zip" dist-windows/
env:
VARIANT_SUFFIX: ${{ inputs.windows_variant == 'cuda' && '-cuda' || '' }}
run: 7z a -tzip "Goose-win32-x64${VARIANT_SUFFIX}.zip" dist-windows/
- name: Upload Windows build
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Goose-win32-x64${{ inputs.windows_variant == 'cuda' && '-cuda' || '' }}
path: Goose-win32-x64${{ inputs.windows_variant == 'cuda' && '-cuda' || '' }}.zip
if-no-files-found: error
overwrite: true
+29 -25
View File
@@ -15,12 +15,8 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
# Permissions for SLSA attestation, AWS OIDC codesigning, and release creation
permissions:
id-token: write # Required for Sigstore OIDC signing and AWS OIDC codesigning
contents: write # Required for creating releases and by actions/checkout
actions: read # Required by bundle-desktop-windows.yml reusable workflow
attestations: write # Required for SLSA build provenance attestations
contents: read
jobs:
# ------------------------------------
@@ -44,11 +40,11 @@ jobs:
echo "version=$VERSION" >> $GITHUB_OUTPUT
# ------------------------------------
# 2) Build CLI for multiple OS/Arch
# 2) Build CLI targets not produced by desktop workflows
# ------------------------------------
build-cli:
build-cli-linux:
needs: [prepare-version]
uses: ./.github/workflows/build-cli.yml
uses: ./.github/workflows/build-cli-linux.yml
with:
version: ${{ needs.prepare-version.outputs.version }}
@@ -58,7 +54,6 @@ jobs:
install-script:
name: Upload Install Script
runs-on: ubuntu-latest
needs: [build-cli]
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
@@ -67,29 +62,33 @@ jobs:
path: download_cli.sh
# ------------------------------------------------------------
# 4) Bundle Desktop App (macOS only)
# 4) Bundle CLI and Desktop (macOS ARM64)
# ------------------------------------------------------------
bundle-desktop:
bundle-macos-arm64:
needs: [prepare-version]
uses: ./.github/workflows/bundle-desktop.yml
uses: ./.github/workflows/bundle-macos.yml
permissions:
id-token: write
contents: read
with:
version: ${{ needs.prepare-version.outputs.version }}
target: aarch64-apple-darwin
package_cli: true
package_desktop: true
signing: false
# ------------------------------------------------------------
# 5) Bundle Desktop App (macOS Intel)
# 5) Bundle CLI and Desktop (macOS x64)
# ------------------------------------------------------------
bundle-desktop-intel:
bundle-macos-x64:
needs: [prepare-version]
uses: ./.github/workflows/bundle-desktop-intel.yml
uses: ./.github/workflows/bundle-macos.yml
permissions:
id-token: write
contents: read
with:
version: ${{ needs.prepare-version.outputs.version }}
target: x86_64-apple-darwin
package_cli: true
package_desktop: true
signing: false
# ------------------------------------------------------------
@@ -102,39 +101,44 @@ jobs:
version: ${{ needs.prepare-version.outputs.version }}
# ------------------------------------------------------------
# 6) Bundle Desktop App (Windows)
# 7) Bundle CLI and Desktop (Windows)
# ------------------------------------------------------------
bundle-desktop-windows:
bundle-windows:
needs: [prepare-version]
uses: ./.github/workflows/bundle-desktop-windows.yml
uses: ./.github/workflows/bundle-windows.yml
with:
version: ${{ needs.prepare-version.outputs.version }}
package_cli: true
package_desktop: true
signing: false
bundle-desktop-windows-cuda:
bundle-windows-cuda:
needs: [prepare-version]
uses: ./.github/workflows/bundle-desktop-windows.yml
uses: ./.github/workflows/bundle-windows.yml
with:
version: ${{ needs.prepare-version.outputs.version }}
package_cli: true
package_desktop: true
signing: false
windows_variant: cuda
# ------------------------------------
# 7) Create/Update GitHub Release
# 8) Create/Update GitHub Release
# ------------------------------------
release:
name: Release
runs-on: ubuntu-latest
needs: [build-cli, install-script, bundle-desktop, bundle-desktop-intel, bundle-desktop-linux, bundle-desktop-windows, bundle-desktop-windows-cuda]
needs: [build-cli-linux, install-script, bundle-macos-arm64, bundle-macos-x64, bundle-desktop-linux, bundle-windows, bundle-windows-cuda]
permissions:
contents: write
id-token: write # Required for Sigstore OIDC signing
attestations: write # Required for SLSA build provenance attestations
steps:
- name: Download all artifacts
- name: Download release artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: '!internal-*'
merge-multiple: true
- name: Attest build provenance
+2 -25
View File
@@ -42,6 +42,8 @@ jobs:
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0
with:
cache: false
- name: Run cargo fmt
run: cargo fmt --check
@@ -62,9 +64,6 @@ jobs:
sudo apt update -y
sudo apt install -y libdbus-1-dev gnome-keyring libxcb1-dev
- name: Cache Cargo artifacts
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
- name: Build and Test
run: |
gnome-keyring-daemon --components=secrets --daemonize --unlock <<< 'foobar'
@@ -166,10 +165,6 @@ jobs:
sudo apt update -y
sudo apt install -y libdbus-1-dev libxcb1-dev
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
key: msrv
- name: Check with MSRV toolchain
run: cargo check --workspace --locked --all-targets
env:
@@ -185,8 +180,6 @@ jobs:
- uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
- name: Lint
run: |
source ./bin/activate-hermit
@@ -212,9 +205,6 @@ jobs:
sudo apt update -y
sudo apt install -y libdbus-1-dev libxcb1-dev
- name: Cache Cargo artifacts
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
- name: Install Node.js Dependencies
run: |
source ./bin/activate-hermit
@@ -242,19 +232,6 @@ jobs:
- name: Checkout Code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
# Temporarily disabled due to GitHub Actions bug on macOS runners
# https://github.com/actions/runner-images/issues/13341
# https://github.com/actions/runner/issues/4134
# - name: Cache pnpm dependencies
# uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
# with:
# path: |
# ui/desktop/node_modules
# .hermit/node/cache
# key: ci-pnpm-cache-v1-${{ runner.os }}-${{ hashFiles('ui/pnpm-lock.yaml') }}
# restore-keys: |
# ci-pnpm-cache-v1-${{ runner.os }}-
- name: Install Dependencies
run: source ../../bin/activate-hermit && pnpm install --frozen-lockfile
working-directory: ui/desktop
-151
View File
@@ -1,151 +0,0 @@
# This workflow is triggered by a comment on PR with the text ".build-cli"
#
# SECURITY: This workflow checks out and builds code from PRs. To prevent
# malicious code execution (GHSA-4h72-4h3w-4587, GHSA-mqm8-hhf6-wvjq),
# we verify the commenter has write access before proceeding.
on:
issue_comment:
types: [created]
workflow_dispatch:
inputs:
pr_number:
description: 'PR number to comment on'
required: true
type: string
# permissions needed for reacting to IssueOps commands on PRs
permissions:
pull-requests: write
checks: read
name: Build CLI
concurrency:
group: ${{ github.workflow }}-${{ (github.event.issue && github.event.issue.number) || github.event.inputs.pr_number }}
cancel-in-progress: true
jobs:
trigger-on-command:
if: >
github.event_name == 'workflow_dispatch' ||
(github.event.issue.pull_request && contains(github.event.comment.body, '.build-cli'))
name: Trigger on ".build-cli" PR comment
runs-on: ubuntu-latest
outputs:
continue: ${{ steps.security_check.outputs.authorized }}
pr_number: ${{ steps.command.outputs.issue_number || github.event.inputs.pr_number }}
head_sha: ${{ steps.set_head_sha.outputs.head_sha || github.sha }}
steps:
# SECURITY: Verify commenter has write access BEFORE any checkout
# This prevents attackers from triggering builds on their own malicious PRs
- name: Verify commenter permissions
id: security_check
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
// workflow_dispatch requires repo write access, so it's inherently safe
if (context.eventName === 'workflow_dispatch') {
core.setOutput('authorized', 'true');
console.log('✅ workflow_dispatch - authorized');
return;
}
const commenter = context.payload.comment.user.login;
console.log(`Checking permissions for: ${commenter}`);
try {
const { data: permission } = await github.rest.repos.getCollaboratorPermissionLevel({
owner: context.repo.owner,
repo: context.repo.repo,
username: commenter
});
const allowed = ['admin', 'maintain', 'write'].includes(permission.permission);
console.log(`Permission level: ${permission.permission}, Authorized: ${allowed}`);
if (!allowed) {
// Post a comment explaining the rejection
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.issue.number,
body: `⚠️ @${commenter} Only repository collaborators with write access can trigger builds.`
});
core.setOutput('authorized', 'false');
} else {
core.setOutput('authorized', 'true');
}
} catch (error) {
console.log(`Permission check failed: ${error.message}`);
core.setOutput('authorized', 'false');
}
- name: Run command action
if: steps.security_check.outputs.authorized == 'true' && github.event_name == 'issue_comment'
uses: github/command@v2.0.3
id: command
with:
command: ".build-cli"
skip_reviews: true
reaction: "eyes"
allowed_contexts: pull_request
- name: Checkout code
if: steps.security_check.outputs.authorized == 'true'
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Get PR head SHA with gh
id: set_head_sha
if: steps.security_check.outputs.authorized == 'true'
run: |
echo "Get PR head SHA with gh"
HEAD_SHA=$(gh pr view "$ISSUE_NUMBER" --json headRefOid -q .headRefOid)
echo "head_sha=$HEAD_SHA" >> $GITHUB_OUTPUT
echo "head_sha=$HEAD_SHA"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ISSUE_NUMBER: ${{ steps.command.outputs.issue_number || github.event.inputs.pr_number }}
build-cli:
needs: [trigger-on-command]
if: ${{ needs.trigger-on-command.outputs.continue == 'true' }}
uses: ./.github/workflows/build-cli.yml
with:
ref: ${{ needs.trigger-on-command.outputs.head_sha }}
pr-comment-cli:
name: PR Comment with CLI builds
runs-on: ubuntu-latest
needs: [trigger-on-command, build-cli]
permissions:
pull-requests: write
steps:
- name: Download CLI artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: goose-*
path: cli-dist
merge-multiple: true
- name: Comment on PR with CLI download links
uses: peter-evans/create-or-update-comment@v5
with:
issue-number: ${{ needs.trigger-on-command.outputs.pr_number }}
body: |
### CLI Builds
Download CLI builds for different platforms:
- [📦 Linux (x86_64, Ubuntu 22.04-compatible)](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/goose-x86_64-unknown-linux-gnu.zip)
- [📦 Linux (aarch64, Ubuntu 22.04-compatible)](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/goose-aarch64-unknown-linux-gnu.zip)
- [📦 Linux musl (x86_64)](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/goose-x86_64-unknown-linux-musl.zip)
- [📦 Linux musl (aarch64)](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/goose-aarch64-unknown-linux-musl.zip)
- [📦 Linux Vulkan (x86_64, Ubuntu 24.04+)](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/goose-x86_64-unknown-linux-gnu-vulkan.zip)
- [📦 Linux Vulkan (aarch64, Ubuntu 24.04+)](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/goose-aarch64-unknown-linux-gnu-vulkan.zip)
- [📦 macOS (x86_64)](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/goose-x86_64-apple-darwin.zip)
- [📦 macOS (aarch64)](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/goose-aarch64-apple-darwin.zip)
- [📦 Windows (x86_64)](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/goose-x86_64-pc-windows-msvc.zip)
- [📦 Windows CUDA (x86_64)](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/goose-x86_64-pc-windows-msvc-cuda.zip)
These links are provided by nightly.link and will work even if you're not logged into GitHub.
@@ -1,115 +0,0 @@
# This workflow is triggered by a comment on PR with the text ".bundle-intel"
# It bundles the Intel Desktop App, then creates a PR comment with a link to download the app.
on:
issue_comment:
types: [created]
workflow_dispatch:
inputs:
pr_number:
description: 'PR number to comment on'
required: true
type: string
# permissions needed for reacting to IssueOps commands on PRs
permissions:
pull-requests: write
checks: read
name: Bundle Intel Desktop App
concurrency:
group: ${{ github.workflow }}-${{ (github.event.issue && github.event.issue.number) || github.event.inputs.pr_number }}
cancel-in-progress: true
jobs:
trigger-on-command:
if: >
github.event_name == 'workflow_dispatch' ||
(github.event.issue.pull_request && contains(github.event.comment.body, '.bundle-intel'))
name: Trigger on ".bundle-intel" PR comment
runs-on: ubuntu-latest
outputs:
continue: 'true'
# Cannot use github.event.pull_request.number since the trigger is 'issue_comment'
pr_number: ${{ steps.command.outputs.issue_number || github.event.inputs.pr_number }}
head_sha: ${{ steps.set_head_sha.outputs.head_sha || github.sha }}
steps:
- name: Run command action
uses: github/command@3442f3fa1efe01bdb024b157083c337902d17372 # v2.0.3
id: command
with:
command: ".bundle-intel"
skip_reviews: true
reaction: "eyes"
allowed_contexts: pull_request
- name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Get PR head SHA with gh
id: set_head_sha
run: |
echo "Get PR head SHA with gh"
HEAD_SHA=$(gh pr view "$ISSUE_NUMBER" --json headRefOid -q .headRefOid)
echo "head_sha=$HEAD_SHA" >> $GITHUB_OUTPUT
echo "head_sha=$HEAD_SHA"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ISSUE_NUMBER: ${{ steps.command.outputs.issue_number }}
bundle-desktop-intel:
# Only run this if ".bundle-intel" command is detected.
needs: [trigger-on-command]
if: ${{ needs.trigger-on-command.outputs.continue == 'true' }}
uses: ./.github/workflows/bundle-desktop-intel.yml
permissions:
id-token: write
contents: read
with:
signing: false
ref: ${{ needs.trigger-on-command.outputs.head_sha }}
pr-comment-intel:
name: PR Comment with macOS Intel App
runs-on: ubuntu-latest
needs: [trigger-on-command, bundle-desktop-intel]
permissions:
pull-requests: write
steps:
- name: Download Intel artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: Goose-darwin-x64
path: intel-dist
- name: Comment on PR with Intel download link
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
with:
issue-number: ${{ needs.trigger-on-command.outputs.pr_number }}
body: |
### macOS Intel Desktop App (x64)
[💻 Download macOS Desktop App (Intel x64, unsigned)](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/Goose-darwin-x64.zip)
**Instructions:**
The easiest way is to just run the following script:
`./scripts/pre-release.sh`
script which will download the latest release (or you can specify the release you need), does the
unzip, xattr to get it out of quarantine and signs it.
If you need to do this manually:
* Download the file
* Unzip
* run `xattr -r -d com.apple.quarantine '/path/to/Goose.app'`
* optionally run `codesign --force --deep --sign - --entitlements ui/desktop/entitlements.plist '/path/to/Goose.app'`
* start the app
The signing step is only needed if you do something that uses mac entitlements like speech to text
This link is provided by nightly.link and will work even if you're not logged into GitHub.
@@ -1,100 +0,0 @@
# This workflow is triggered by a comment on PR with the text ".bundle-windows"
# It bundles the Windows Desktop App, then creates a PR comment with a link to download the app.
on:
issue_comment:
types: [created]
workflow_dispatch:
inputs:
pr_number:
description: 'PR number to comment on'
required: true
type: string
# permissions needed for reacting to IssueOps commands on PRs and AWS OIDC authentication
permissions:
pull-requests: write
checks: read
id-token: write # Required for AWS OIDC authentication in called workflow
contents: read # Required by actions/checkout in called workflow
actions: read # May be needed for some workflows
name: Bundle Windows Desktop App
concurrency:
group: ${{ github.workflow }}-${{ (github.event.issue && github.event.issue.number) || github.event.inputs.pr_number }}
cancel-in-progress: true
jobs:
trigger-on-command:
if: >
github.event_name == 'workflow_dispatch' ||
(github.event.issue.pull_request && contains(github.event.comment.body, '.bundle-windows'))
name: Trigger on ".bundle-windows" PR comment
runs-on: ubuntu-latest
outputs:
continue: 'true'
# Cannot use github.event.pull_request.number since the trigger is 'issue_comment'
pr_number: ${{ steps.command.outputs.issue_number || github.event.inputs.pr_number }}
head_sha: ${{ steps.set_head_sha.outputs.head_sha || github.sha }}
steps:
- name: Run command action
uses: github/command@3442f3fa1efe01bdb024b157083c337902d17372 # v2.0.3
id: command
with:
command: ".bundle-windows"
skip_reviews: true
reaction: "eyes"
allowed_contexts: pull_request
- name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Get PR head SHA with gh
id: set_head_sha
run: |
echo "Get PR head SHA with gh"
HEAD_SHA=$(gh pr view "$ISSUE_NUMBER" --json headRefOid -q .headRefOid)
echo "head_sha=$HEAD_SHA" >> $GITHUB_OUTPUT
echo "head_sha=$HEAD_SHA"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ISSUE_NUMBER: ${{ steps.command.outputs.issue_number }}
bundle-desktop-windows:
# Only run this if ".bundle-windows" command is detected.
needs: [trigger-on-command]
if: ${{ needs.trigger-on-command.outputs.continue == 'true' }}
uses: ./.github/workflows/bundle-desktop-windows.yml
with:
signing: false
ref: ${{ needs.trigger-on-command.outputs.head_sha }}
pr-comment-windows:
name: PR Comment with Windows App
runs-on: ubuntu-latest
needs: [trigger-on-command, bundle-desktop-windows]
permissions:
pull-requests: write
steps:
- name: Download Windows artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: Goose-win32-x64
path: windows-dist
- name: Comment on PR with Windows download link
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
with:
issue-number: ${{ needs.trigger-on-command.outputs.pr_number }}
body: |
### Windows Desktop App
[🪟 Download Windows Desktop App (x64, signed)](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/Goose-win32-x64.zip)
**Instructions:**
After downloading, unzip the file and run Goose.exe. The app is signed for Windows.
This link is provided by nightly.link and will work even if you're not logged into GitHub.
-206
View File
@@ -1,206 +0,0 @@
# This workflow is triggered by a comment on PR with the text ".bundle"
# It bundles the ARM64 Desktop App, then creates a PR comment with a link to download the app.
#
# SECURITY: This workflow checks out and builds code from PRs. To prevent
# malicious code execution (GHSA-4h72-4h3w-4587), we verify the commenter
# has write access before proceeding.
on:
issue_comment:
types: [created]
workflow_dispatch:
inputs:
pr_number:
description: 'PR number to comment on'
required: true
type: string
# permissions needed for reacting to IssueOps commands on PRs
permissions:
pull-requests: write
checks: read
name: Bundle ARM64 Desktop App
concurrency:
group: ${{ github.workflow }}-${{ (github.event.issue && github.event.issue.number) || github.event.inputs.pr_number }}
cancel-in-progress: true
jobs:
trigger-on-command:
if: >
github.event_name == 'workflow_dispatch' ||
(github.event.issue.pull_request && contains(github.event.comment.body, '.bundle'))
name: Trigger on ".bundle" PR comment
runs-on: ubuntu-latest
outputs:
continue: ${{ steps.security_check.outputs.authorized }}
pr_number: ${{ steps.command.outputs.issue_number || github.event.inputs.pr_number }}
pr_sha: ${{ steps.get_pr_info.outputs.sha }}
steps:
# SECURITY: Verify commenter has write access BEFORE any checkout
# This prevents attackers from triggering builds on their own malicious PRs
- name: Verify commenter permissions
id: security_check
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
// workflow_dispatch requires repo write access, so it's inherently safe
if (context.eventName === 'workflow_dispatch') {
core.setOutput('authorized', 'true');
console.log('✅ workflow_dispatch - authorized');
return;
}
const commenter = context.payload.comment.user.login;
console.log(`Checking permissions for: ${commenter}`);
try {
const { data: permission } = await github.rest.repos.getCollaboratorPermissionLevel({
owner: context.repo.owner,
repo: context.repo.repo,
username: commenter
});
const allowed = ['admin', 'maintain', 'write'].includes(permission.permission);
console.log(`Permission level: ${permission.permission}, Authorized: ${allowed}`);
if (!allowed) {
// Post a comment explaining the rejection
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.issue.number,
body: `⚠️ @${commenter} Only repository collaborators with write access can trigger builds.`
});
core.setOutput('authorized', 'false');
} else {
core.setOutput('authorized', 'true');
}
} catch (error) {
console.log(`Permission check failed: ${error.message}`);
core.setOutput('authorized', 'false');
}
- name: Debug workflow trigger
if: steps.security_check.outputs.authorized == 'true'
env:
WORKFLOW_NAME: ${{ github.workflow }}
WORKFLOW_REF: ${{ github.ref }}
EVENT_NAME: ${{ github.event_name }}
EVENT_ACTION: ${{ github.event.action }}
ACTOR: ${{ github.actor }}
REPOSITORY: ${{ github.repository }}
run: |
echo "=== Workflow Trigger Info ==="
echo "Workflow: ${WORKFLOW_NAME}"
echo "Ref: ${WORKFLOW_REF}"
echo "Event: ${EVENT_NAME}"
echo "Action: ${EVENT_ACTION}"
echo "Actor: ${ACTOR}"
echo "Repository: ${REPOSITORY}"
- name: Run command action
if: steps.security_check.outputs.authorized == 'true'
uses: github/command@3442f3fa1efe01bdb024b157083c337902d17372 # v2.0.3
id: command
with:
command: ".bundle"
skip_reviews: true
reaction: "eyes"
allowed_contexts: pull_request
# Get the PR's SHA
- name: Get PR info
id: get_pr_info
if: steps.security_check.outputs.authorized == 'true'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
let prNumber;
if (context.eventName === 'workflow_dispatch') {
prNumber = context.payload.inputs.pr_number;
} else {
prNumber = context.payload.issue.number;
}
if (!prNumber) {
throw new Error('No PR number found');
}
console.log('Using PR number:', prNumber);
const { data: pr } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: parseInt(prNumber, 10)
});
console.log('PR Details:', {
number: pr.number,
head: {
ref: pr.head.ref,
sha: pr.head.sha,
label: pr.head.label
},
base: {
ref: pr.base.ref,
sha: pr.base.sha,
label: pr.base.label
}
});
core.setOutput('sha', pr.head.sha);
bundle-desktop:
needs: [trigger-on-command]
if: ${{ needs.trigger-on-command.outputs.continue == 'true' }}
uses: ./.github/workflows/bundle-desktop.yml
permissions:
id-token: write
contents: read
with:
signing: false
ref: ${{ needs.trigger-on-command.outputs.pr_sha }}
pr-comment-arm64:
name: PR Comment with macOS ARM64 App
runs-on: ubuntu-latest
needs: [trigger-on-command, bundle-desktop]
permissions:
pull-requests: write
steps:
- name: Download ARM64 artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: Goose-darwin-arm64
path: arm64-dist
- name: Comment on PR with ARM64 download link
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
with:
issue-number: ${{ needs.trigger-on-command.outputs.pr_number }}
body: |
### macOS ARM64 Desktop App (Apple Silicon)
[📱 Download macOS Desktop App (arm64, unsigned)](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/Goose-darwin-arm64.zip)
**Instructions:**
The easiest way is to just run the following script:
`./scripts/pre-release.sh`
script which will download the latest release (or you can specify the release you need), does the
unzip, xattr to get it out of quarantine and signs it.
If you need to do this manually:
* Download the file
* Unzip
* run `xattr -r -d com.apple.quarantine '/path/to/Goose.app'`
* optionally run `codesign --force --deep --sign - --entitlements ui/desktop/entitlements.plist '/path/to/Goose.app'`
* start the app
The signing step is only needed if you do something that uses mac entitlements like speech to text
-3
View File
@@ -62,9 +62,6 @@ jobs:
sudo apt update -y
sudo apt install -y libdbus-1-dev gnome-keyring libxcb1-dev
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
- name: Build Binary for Smoke Tests
run: |
cargo build --bin goose
+4 -3
View File
@@ -7,9 +7,7 @@ on:
concurrency: ${{ github.workflow }}-${{ github.ref }}
permissions:
contents: write
pull-requests: write
id-token: write # Required for npm trusted publishing (OIDC)
contents: read
jobs:
build-cli:
@@ -39,6 +37,7 @@ jobs:
- name: Download freshly-built goose CLI binaries
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: goose-*
path: /tmp/cli-artifacts
- name: Place binaries into goose-binary packages
@@ -146,6 +145,8 @@ jobs:
runs-on: ubuntu-latest
needs: [build]
environment: npm-production-publishing
permissions:
id-token: write # Required for npm trusted publishing (OIDC)
steps:
- name: Download built packages
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
-3
View File
@@ -62,9 +62,6 @@ jobs:
if: runner.os != 'Linux'
uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4
- name: Cache Cargo artifacts
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
- name: Build wheel
shell: bash
run: just --justfile crates/goose-sdk/justfile python-wheel
+5 -2
View File
@@ -6,10 +6,13 @@ on:
jobs:
bundle-desktop:
if: startsWith(github.head_ref, 'release/')
uses: ./.github/workflows/bundle-desktop.yml
uses: ./.github/workflows/bundle-macos.yml
permissions:
id-token: write
contents: read
with:
target: aarch64-apple-darwin
package_cli: false
package_desktop: true
comment-on-pr:
needs: bundle-desktop
+32 -34
View File
@@ -11,11 +11,7 @@ on:
name: Release
permissions:
id-token: write # Required for Sigstore OIDC signing and AWS OIDC (Windows signing)
contents: write # Required for creating releases and by actions/checkout
actions: read # May be needed for some workflows
pull-requests: write # Required for npm publish workflow
attestations: write # Required for SLSA build provenance attestations
contents: read
env:
# Set this repository Actions variable to "true" in GitHub Settings > Secrets and variables
@@ -28,10 +24,10 @@ concurrency:
jobs:
# ------------------------------------
# 1) Build CLI for multiple OS/Arch
# 1) Build CLI targets not produced by desktop workflows
# ------------------------------------
build-cli:
uses: ./.github/workflows/build-cli.yml
build-cli-linux:
uses: ./.github/workflows/build-cli-linux.yml
# ------------------------------------
# 2) Upload Install CLI Script
@@ -39,7 +35,6 @@ jobs:
install-script:
name: Upload Install Script
runs-on: ubuntu-latest
needs: [build-cli]
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
@@ -48,30 +43,30 @@ jobs:
path: download_cli.sh
# ------------------------------------------------------------
# 3) Bundle Desktop App (macOS)
# 3) Bundle CLI and Desktop (macOS ARM64)
# ------------------------------------------------------------
bundle-desktop:
uses: ./.github/workflows/bundle-desktop.yml
bundle-macos-arm64:
uses: ./.github/workflows/bundle-macos.yml
permissions:
id-token: write
contents: read
with:
target: aarch64-apple-darwin
package_cli: true
package_desktop: true
signing: ${{ startsWith(github.ref, 'refs/tags/') }}
environment: ${{ startsWith(github.ref, 'refs/tags/') && 'signing' || '' }}
secrets: inherit
# ------------------------------------------------------------
# 4) Bundle Desktop App (macOS)
# 4) Bundle CLI and Desktop (macOS x64)
# ------------------------------------------------------------
bundle-desktop-intel:
uses: ./.github/workflows/bundle-desktop-intel.yml
bundle-macos-x64:
uses: ./.github/workflows/bundle-macos.yml
permissions:
id-token: write
contents: read
with:
target: x86_64-apple-darwin
package_cli: true
package_desktop: true
signing: ${{ startsWith(github.ref, 'refs/tags/') }}
environment: ${{ startsWith(github.ref, 'refs/tags/') && 'signing' || '' }}
secrets: inherit
# ------------------------------------------------------------
# 5) Bundle Desktop App (Linux)
@@ -79,29 +74,29 @@ jobs:
bundle-desktop-linux:
uses: ./.github/workflows/bundle-desktop-linux.yml
# # ------------------------------------------------------------
# # 6) Bundle Desktop App (Windows)
# # ------------------------------------------------------------
bundle-desktop-windows:
uses: ./.github/workflows/bundle-desktop-windows.yml
# ------------------------------------------------------------
# 6) Bundle CLI and Desktop (Windows)
# ------------------------------------------------------------
bundle-windows:
uses: ./.github/workflows/bundle-windows.yml
permissions:
id-token: write
contents: read
actions: read
with:
package_cli: true
package_desktop: true
signing: ${{ startsWith(github.ref, 'refs/tags/') }}
secrets: inherit
bundle-desktop-windows-cuda:
uses: ./.github/workflows/bundle-desktop-windows.yml
bundle-windows-cuda:
uses: ./.github/workflows/bundle-windows.yml
permissions:
id-token: write
contents: read
actions: read
with:
package_cli: true
package_desktop: true
signing: ${{ startsWith(github.ref, 'refs/tags/') }}
windows_variant: cuda
secrets: inherit
# ------------------------------------
# 7) Create/Update GitHub Release
@@ -110,17 +105,20 @@ jobs:
name: Release
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
needs: [build-cli, install-script, bundle-desktop, bundle-desktop-intel, bundle-desktop-linux, bundle-desktop-windows, bundle-desktop-windows-cuda]
needs: [build-cli-linux, install-script, bundle-macos-arm64, bundle-macos-x64, bundle-desktop-linux, bundle-windows, bundle-windows-cuda]
permissions:
contents: write
id-token: write # Required for Sigstore OIDC signing
attestations: write # Required for SLSA build provenance attestations
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Download all artifacts
- name: Download release artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: '!internal-*'
merge-multiple: true
- name: Generate macOS update manifest
-209
View File
@@ -1,209 +0,0 @@
name: Daily Test Coverage Finder
on:
# schedule:
# # Run daily at 2 AM UTC - PAUSED
# - cron: '0 2 * * *'
workflow_dispatch:
inputs:
dry_run:
description: 'Dry run (no PR creation)'
required: false
default: false
type: boolean
permissions:
contents: write
pull-requests: write
jobs:
find-untested-code:
runs-on: ubuntu-latest
container:
image: ghcr.io/aaif-goose/goose:latest
options: --user root
env:
GOOSE_PROVIDER: ${{ vars.GOOSE_PROVIDER || 'openai' }}
GOOSE_MODEL: ${{ vars.GOOSE_MODEL || 'gpt-5' }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
HOME: /tmp/goose-home
steps:
- name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0
- name: Install build and analysis tools
run: |
apt-get update
apt-get install -y jq ripgrep build-essential
- name: Find untested code and create working test
id: find_untested
run: |
# Ensure the HOME directory structure exists
mkdir -p $HOME/.local/share/goose/sessions
mkdir -p $HOME/.config/goose
# Create analysis and test creation script
cat << 'EOF' > /tmp/create_working_test.txt
Your task is to find ONE untested function in the Rust codebase and create a working test for it.
Requirements:
1. The function MUST be in the crates/ directory
2. It MUST have actual logic (not just a simple getter/setter)
3. It MUST not already have a test
4. Prefer functions with complexity but that are still testable in isolation
5. Focus on the goose crate first, then goose-cli, then others
Process:
0. Immediately write these requirements in your TODO list tool and periodically check against them
1. Find a suitable untested function (use your `analyze` tool and ripgrep)
2. Write a comprehensive unit test for it
3. Apply your changes to the codebase
4. Run the test with: cargo test --test <test_name>
5. If the test fails:
- Read and understand the error message
- Fix the test code
- Apply the fix and run the test again
- Repeat up to 3 times until the test passes
6. Once the test passes, run `cargo fmt` to format all the code properly
7. After formatting, save the final changes as a git diff to /tmp/test_addition.patch
8. If successful, write the name of the function you tested to /tmp/function_tested.txt (just the function name, e.g., "check_tool_call" or "MyStruct::my_method")
9. Only create the patch file if the test actually passes
Important:
- Only add ONE test for ONE function
- The test MUST compile and pass before creating the patch
- Keep changes minimal and focused
- Include a descriptive test name that explains what is being tested
EOF
goose run -i /tmp/create_working_test.txt --with-builtin developer
# Debug: Check what files were created
echo "Checking for patch file..."
if [ -f /tmp/test_addition.patch ]; then
echo "Patch file exists"
echo "Patch size: $(wc -c < /tmp/test_addition.patch) bytes"
echo "First few lines of patch:"
head -5 /tmp/test_addition.patch || true
else
echo "No patch file found at /tmp/test_addition.patch"
fi
# Check for new commits that Goose might have made
COMMITS_AHEAD=$(git rev-list HEAD --not --remotes=origin --count 2>/dev/null || echo "0")
echo "Commits ahead of origin: $COMMITS_AHEAD"
# Check if we have changes to create a PR from
# Either: 1) A patch file exists, OR 2) There are new commits
if [ -f /tmp/test_addition.patch ] && [ -s /tmp/test_addition.patch ] || [ "$COMMITS_AHEAD" -gt 0 ]; then
echo "Changes detected (patch file or new commits)"
echo "Attempting to apply patch..."
# Apply the patch for the PR
git apply /tmp/test_addition.patch 2>/dev/null || echo "Patch already applied or changes already present"
echo "patch_created=true" >> $GITHUB_OUTPUT
echo "Test creation successful - patch file created"
# Try to get the function name from Goose's output file
if [ -f /tmp/function_tested.txt ]; then
FUNC_NAME=$(cat /tmp/function_tested.txt | head -1)
else
# Fallback: Extract from test name in the actual changes (staged or committed)
# Try staged changes first, then last commit, then patch file
FUNC_NAME=$(git diff --cached | grep "^+.*fn test_" | head -1 | sed 's/.*fn test_//' | sed 's/(.*//' || git diff HEAD~1 | grep "^+.*fn test_" | head -1 | sed 's/.*fn test_//' | sed 's/(.*//' || grep "fn test_" /tmp/test_addition.patch 2>/dev/null | head -1 | sed 's/.*fn test_//' | sed 's/(.*//' || echo "function")
fi
# Clean up the function name (remove any trailing whitespace or special chars)
FUNC_NAME=$(echo "$FUNC_NAME" | tr -d '\n\r' | sed 's/[[:space:]]*$//')
echo "function_name=${FUNC_NAME}" >> $GITHUB_OUTPUT
else
echo "patch_created=false" >> $GITHUB_OUTPUT
echo "No patch file created - either no suitable function found or test failed"
fi
- name: Extract token metrics
id: metrics
run: |
# Find the most recently created session file in the goose sessions directory
SESSION_DIR="$HOME/.local/share/goose/sessions"
if [ -d "$SESSION_DIR" ]; then
SESSION_FILE=$(ls -t "$SESSION_DIR"/*.jsonl 2>/dev/null | head -1)
if [ -f "$SESSION_FILE" ]; then
echo "Found session file: $SESSION_FILE"
# Current context size metrics
TOKENS=$(head -1 "$SESSION_FILE" | jq -r '.total_tokens // 0')
INPUT_TOKENS=$(head -1 "$SESSION_FILE" | jq -r '.input_tokens // 0')
OUTPUT_TOKENS=$(head -1 "$SESSION_FILE" | jq -r '.output_tokens // 0')
echo "total_tokens=${TOKENS}" >> $GITHUB_OUTPUT
echo "input_tokens=${INPUT_TOKENS}" >> $GITHUB_OUTPUT
echo "output_tokens=${OUTPUT_TOKENS}" >> $GITHUB_OUTPUT
# Accumulated API usage metrics
ACC_TOKENS=$(head -1 "$SESSION_FILE" | jq -r '.accumulated_total_tokens // 0')
ACC_INPUT_TOKENS=$(head -1 "$SESSION_FILE" | jq -r '.accumulated_input_tokens // 0')
ACC_OUTPUT_TOKENS=$(head -1 "$SESSION_FILE" | jq -r '.accumulated_output_tokens // 0')
echo "accumulated_total_tokens=${ACC_TOKENS}" >> $GITHUB_OUTPUT
echo "accumulated_input_tokens=${ACC_INPUT_TOKENS}" >> $GITHUB_OUTPUT
echo "accumulated_output_tokens=${ACC_OUTPUT_TOKENS}" >> $GITHUB_OUTPUT
echo "Token usage - Total: ${TOKENS}, Input: ${INPUT_TOKENS}, Output: ${OUTPUT_TOKENS}"
echo "Accumulated usage - Total: ${ACC_TOKENS}, Input: ${ACC_INPUT_TOKENS}, Output: ${ACC_OUTPUT_TOKENS}"
fi
fi
- name: Create Pull Request
if: steps.find_untested.outputs.patch_created == 'true' && github.event.inputs.dry_run != 'true'
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "test: add test for ${{ steps.find_untested.outputs.function_name }}"
title: "test: add test coverage for ${{ steps.find_untested.outputs.function_name }}"
draft: true
body: |
## 🤖 Automated Test Addition
This PR was automatically generated by goose to improve test coverage.
### What changed?
Added a unit test for a previously untested function.
### Why?
Part of our daily automated test coverage improvement initiative. goose analyzes the codebase to find untested but important functions and creates focused unit tests for them.
### Review checklist
- [ ] Test is meaningful and actually tests the function
- [ ] Test name is descriptive
- [ ] Test passes locally
- [ ] No unnecessary changes included
### Metrics
#### Current Context Size
- **Total**: ${{ steps.metrics.outputs.total_tokens }} tokens
- **Input**: ${{ steps.metrics.outputs.input_tokens }} tokens
- **Output**: ${{ steps.metrics.outputs.output_tokens }} tokens
#### API Usage (Billable)
- **Total**: ${{ steps.metrics.outputs.accumulated_total_tokens }} tokens
- **Input**: ${{ steps.metrics.outputs.accumulated_input_tokens }} tokens
- **Output**: ${{ steps.metrics.outputs.accumulated_output_tokens }} tokens
---
*Generated by the Daily Test Coverage Finder workflow*
branch: goose/test-coverage-${{ github.run_number }}
delete-branch: true
labels: |
goose-generated
test
automated
- name: Summary
if: always()
env:
PATCH_CREATED: ${{ steps.find_untested.outputs.patch_created }}
FUNCTION_NAME: ${{ steps.find_untested.outputs.function_name }}
run: |
if [ "$PATCH_CREATED" = "true" ]; then
echo "✅ Successfully found untested code and created a test"
echo "📝 Function tested: $FUNCTION_NAME"
else
echo "️ No suitable untested code found today"
fi
@@ -1,255 +0,0 @@
name: Update Hacktoberfest Leaderboard
on:
schedule:
# Runs every hour at the start of the hour during October (UTC)
- cron: '0 * * 10 *'
workflow_dispatch:
jobs:
update-leaderboard:
runs-on: ubuntu-latest
if: github.repository == 'aaif-goose/goose'
permissions:
contents: read
pull-requests: read
issues: write
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Update Leaderboard
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const issueNumber = 4775; // Update if needed
// Track the Goose repo only
const REPOS = [
'aaif-goose/goose'
];
const POINT_VALUES = { small: 5, medium: 10, large: 15 };
const calculatePoints = (labels) => {
const size = labels.find(label => POINT_VALUES[label.name.toLowerCase()]);
return size ? POINT_VALUES[size.name.toLowerCase()] : POINT_VALUES.small;
};
const getBiggestPRSize = (prs) => {
const sizes = prs.map(pr => {
const sizeLabel = pr.labels.find(label => POINT_VALUES[label.name.toLowerCase()]);
return sizeLabel ? sizeLabel.name.toLowerCase() : 'small';
});
if (sizes.includes('large')) return 'large';
if (sizes.includes('medium')) return 'medium';
return 'small';
};
// Strict October window (UTC)
const now = new Date();
const year = now.getUTCFullYear();
const startDate = new Date(Date.UTC(year, 9, 1, 0, 0, 0)); // Oct 1, 00:00:00 UTC
const endDate = new Date(Date.UTC(year, 9, 31, 23, 59, 59)); // Oct 31, 23:59:59 UTC
const fetchRecentPRs = async (fullRepo) => {
try {
console.log(`Fetching recent PRs for ${fullRepo}`);
const [repoOwner, repoName] = fullRepo.split('/');
let allPRs = [];
let page = 1;
// Paginate through closed PRs sorted by updated desc
while (true) {
const { data: prs } = await github.rest.pulls.list({
owner: repoOwner,
repo: repoName,
state: 'closed',
sort: 'updated',
direction: 'desc',
per_page: 100,
page
});
allPRs = allPRs.concat(prs);
if (prs.length < 100) break;
// Early exit if the oldest updated item on this page precedes October
const oldestUpdated = new Date(prs[prs.length - 1].updated_at);
if (oldestUpdated < startDate) break;
page++;
}
console.log(`Fetched ${allPRs.length} PRs for ${fullRepo}`);
// Strict: must be merged in October AND have label exactly "hacktoberfest"
const hacktoberfestPRs = allPRs
.filter(pr => {
if (!pr.merged_at) return false;
const mergedAt = new Date(pr.merged_at);
const inOctober = mergedAt >= startDate && mergedAt <= endDate;
const isHacktoberfest = pr.labels.some(
label => label.name.toLowerCase() === 'hacktoberfest'
);
return inOctober && isHacktoberfest;
})
.map(pr => ({
user: pr.user.login,
points: calculatePoints(pr.labels),
repo: fullRepo,
prNumber: pr.number,
prTitle: pr.title,
labels: pr.labels
}));
return hacktoberfestPRs;
} catch (error) {
console.warn(`Error fetching PRs for ${fullRepo}: ${error.message}`);
return [];
}
};
const generateLeaderboard = async () => {
try {
const allPRs = (await Promise.all(REPOS.map(fetchRecentPRs))).flat();
const leaderboard = allPRs.reduce((acc, pr) => {
if (!acc[pr.user]) {
acc[pr.user] = { points: 0, prs: 0, userPRs: [] };
}
acc[pr.user].points += pr.points;
acc[pr.user].prs += 1;
acc[pr.user].userPRs.push(pr);
return acc;
}, {});
// Tie-breaker rank for biggest PR size
const sizeOrder = { small: 1, medium: 2, large: 3 };
const sorted = Object.entries(leaderboard)
.sort(([, a], [, b]) => {
if (b.points !== a.points) return b.points - a.points;
const aBig = getBiggestPRSize(a.userPRs);
const bBig = getBiggestPRSize(b.userPRs);
return sizeOrder[bBig] - sizeOrder[aBig];
})
.map(([username, data], index) => ({
rank: index + 1,
username,
points: data.points,
prs: data.prs,
biggestPR: getBiggestPRSize(data.userPRs)
}));
return sorted;
} catch (error) {
console.error(`Error generating leaderboard: ${error.message}`);
return [];
}
};
const updateIssue = async (leaderboardData) => {
const rankStars = (rank) => (rank <= 3 ? '⭐⭐⭐' : rank <= 10 ? '⭐' : '');
const tableRows = leaderboardData
.map(entry => `| ${entry.rank} ${rankStars(entry.rank)} | @${entry.username} | ${entry.points} | ${entry.prs} | ${entry.biggestPR} |`)
.join('\n');
const issueBody = `# 🏆 Hacktoberfest 2025 Goose Leaderboard 🏆
Hello, lovely contributors! As Hacktoberfest 2025 and the crisp Fall breeze refreshes us, we wanted to make the contribution process extra fun. Check our live leaderboard below to see who our top contributors are this year in real-time. Not only does this recognize your efforts, it also brings an amplified competitive vibe with each contribution.
### 🌟 **Current Rankings:**
| Rank | Contributor | Points | PRs | Biggest PR to Date |
|------|-------------|--------|-----|--------------------|
${tableRows}
### 📜 How It Works:
The top 20 contributors will earn the first ever goose swag from the swag shop along with LLM credits! To earn your place in the leaderboard, we have created a points system that is explained below. As you complete a task by successfully merging a PR, you will automatically be granted a certain # of points.
#### 💯 Point System
| Weight | Points Awarded | Description |
|---------|-------------|-------------|
| 🐭 **Small** | 5 points | For smaller tasks that take limited time to complete and/or don't require any product knowledge. |
| 🐰 **Medium** | 10 points | For average tasks that take additional time to complete and/or require some product knowledge. |
| 🐂 **Large** | 15 points | For heavy tasks that takes lots of time to complete and/or possibly require deep product knowledge. |
#### 🎁 Rewards
- Made it to the **top 5**? Our Top 5 Contributors with the most points will be awarded $100 gift cards to our brand new goose swag shop and $100 of LLM credits!
- Reached the top **6-10**? Our Top 6-10 Contributors with the most points will be awarded $50 gift cards to our brand new goose swag shop and $50 of LLM credits!
- Landed in the top **11-20**? Our Top 11-20 Contributors with the most points will be awarded $25 of LLM credits! Keep an eye on your progress to make sure you're one step ahead!
### FAQ
- **Frequency of Updates:** The leaderboard will be updated every hour.
- **Criteria:** Rankings are based on how many points you earn across all approved PRs in the goose repo. To ensure your PRs are successfully merged:
- Ensure your contributions are aligned with our project's Code of Conduct.
- Refer to the goose repo's Contributing Guide.
- **Tie-Breakers:** In the event of a tie in total points, the contributor with the highest value single contribution (large > medium > small) will be ranked higher.
### 🚀 Get Featured:
Want to see your name climbing our ranks?
Explore our issues with the labels \`good-first-issue\`, \`no-code\` and \`hacktoberfest\` in the goose repo' Project Hub:
- **goose**
- Hacktoberfest Project Hub
- Contributing Guide
Excited to see everyone's hard work. Thank you so much for your invaluable contributions, and let the fun competition begin!
Last updated: ${new Date().toUTCString()}`;
try {
await github.rest.issues.update({
owner,
repo,
issue_number: issueNumber,
body: issueBody
});
console.log("Issue updated successfully!");
} catch (err) {
console.error(`Failed to update issue #${issueNumber}: ${err.message}`);
throw err;
}
};
// Main execution
const leaderboardData = await generateLeaderboard();
if (leaderboardData.length > 0) {
await updateIssue(leaderboardData);
} else {
console.log("No leaderboard data to update.");
const emptyIssueBody = `# 🏆 Hacktoberfest 2025 Goose Leaderboard 🏆
Hello, lovely contributors! As Hacktoberfest 2025 and the crisp Fall breeze refreshes us, we wanted to make the contribution process extra fun. Check our live leaderboard below to see who our top contributors are this year in real-time. Not only does this recognize everyone's efforts, it also brings an amplified competitive vibe with each contribution.
### 🌟 **Current Rankings:**
| Rank | Contributor | Points | PRs | Biggest PR to Date |
|------|-------------|--------|-----|--------------------|
| | | | | |
No qualifying PRs found at this time. Check back soon!
Last updated: ${new Date().toUTCString()}`;
try {
await github.rest.issues.update({
owner,
repo,
issue_number: issueNumber,
body: emptyIssueBody
});
console.log("Updated issue with empty leaderboard message.");
} catch (err) {
console.error(`Failed to update issue #${issueNumber} (empty state): ${err.message}`);
throw err;
}
}
+2 -2
View File
@@ -25,7 +25,7 @@ release-binary:
# Build Windows executable on a Windows host
[unix]
release-windows:
@echo "just release-windows requires a Windows host because Goose Windows releases build the MSVC target. Use .github/workflows/bundle-desktop-windows.yml for CI builds."
@echo "just release-windows requires a Windows host because Goose Windows releases build the MSVC target. Use .github/workflows/bundle-windows.yml for CI builds."
@exit 1
[windows]
@@ -197,7 +197,7 @@ make-ui:
# make GUI with latest Windows binary on a Windows host
[unix]
make-ui-windows:
@echo "just make-ui-windows requires a Windows host because Goose Windows releases build the MSVC target. Use .github/workflows/bundle-desktop-windows.yml for CI builds."
@echo "just make-ui-windows requires a Windows host because Goose Windows releases build the MSVC target. Use .github/workflows/bundle-windows.yml for CI builds."
@exit 1
[windows]
+7
View File
@@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
version=${1:?Version is required}
sed -i.bak "s/^version = \".*\"/version = \"${version}\"/" Cargo.toml
rm -f Cargo.toml.bak