unsloth/.github/workflows/studio-mac-ui-smoke.yml
dependabot[bot] fd6ecee2a3
Bump the actions group across 1 directory with 12 updates
Bumps the actions group with 12 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [actions/checkout](https://github.com/actions/checkout) | `4.2.2` | `7.0.1` |
| [actions/setup-python](https://github.com/actions/setup-python) | `6.2.0` | `7.0.0` |
| [actions/setup-node](https://github.com/actions/setup-node) | `6.4.0` | `7.0.0` |
| [actions/cache/restore](https://github.com/actions/cache) | `5.0.5` | `6.1.0` |
| [actions/cache/save](https://github.com/actions/cache) | `5.0.5` | `6.1.0` |
| [actions/upload-artifact](https://github.com/actions/upload-artifact) | `4.6.1` | `7.0.1` |
| [step-security/harden-runner](https://github.com/step-security/harden-runner) | `2.19.1` | `2.20.0` |
| [ossf/scorecard-action](https://github.com/ossf/scorecard-action) | `2.4.1` | `2.4.3` |
| [github/codeql-action](https://github.com/github/codeql-action) | `3` | `4` |
| [tauri-apps/tauri-action](https://github.com/tauri-apps/tauri-action) | `0.6.2` | `1.0.0` |
| [trufflesecurity/trufflehog](https://github.com/trufflesecurity/trufflehog) | `3.95.3` | `3.95.9` |
| [actions/stale](https://github.com/actions/stale) | `10.2.0` | `10.4.0` |



Updates `actions/checkout` from 4.2.2 to 7.0.1
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4.2.2...3d3c42e5aac5ba805825da76410c181273ba90b1)

Updates `actions/setup-python` from 6.2.0 to 7.0.0
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v6.2.0...5fda3b95a4ea91299a34e894583c3862153e4b97)

Updates `actions/setup-node` from 6.4.0 to 7.0.0
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](48b55a011b...8207627860)

Updates `actions/cache/restore` from 5.0.5 to 6.1.0
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](27d5ce7f10...55cc834586)

Updates `actions/cache/save` from 5.0.5 to 6.1.0
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](27d5ce7f10...55cc834586)

Updates `actions/upload-artifact` from 4.6.1 to 7.0.1
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4.6.1...043fb46d1a93c77aae656e7c1c64a875d1fc6a0a)

Updates `step-security/harden-runner` from 2.19.1 to 2.20.0
- [Release notes](https://github.com/step-security/harden-runner/releases)
- [Commits](a5ad31d6a1...bf7454d06d)

Updates `ossf/scorecard-action` from 2.4.1 to 2.4.3
- [Release notes](https://github.com/ossf/scorecard-action/releases)
- [Changelog](https://github.com/ossf/scorecard-action/blob/main/RELEASE.md)
- [Commits](f49aabe0b5...4eaacf0543)

Updates `github/codeql-action` from 3 to 4
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v3...v4)

Updates `tauri-apps/tauri-action` from 0.6.2 to 1.0.0
- [Release notes](https://github.com/tauri-apps/tauri-action/releases)
- [Changelog](https://github.com/tauri-apps/tauri-action/blob/dev/CHANGELOG.md)
- [Commits](84b9d35b5f...1deb371b0c)

Updates `trufflesecurity/trufflehog` from 3.95.3 to 3.95.9
- [Release notes](https://github.com/trufflesecurity/trufflehog/releases)
- [Commits](37b77001d0...27b0417c16)

Updates `actions/stale` from 10.2.0 to 10.4.0
- [Release notes](https://github.com/actions/stale/releases)
- [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md)
- [Commits](b5d41d4e1d...1e223db275)

---
updated-dependencies:
- dependency-name: actions/cache/restore
  dependency-version: 6.1.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: actions/cache/save
  dependency-version: 6.1.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: actions/checkout
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: actions/setup-node
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: actions/setup-python
  dependency-version: 6.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
- dependency-name: actions/stale
  dependency-version: 10.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
- dependency-name: actions/upload-artifact
  dependency-version: 7.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: github/codeql-action
  dependency-version: 4.37.1
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: ossf/scorecard-action
  dependency-version: 2.4.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
- dependency-name: step-security/harden-runner
  dependency-version: 2.20.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
- dependency-name: tauri-apps/tauri-action
  dependency-version: 1.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: trufflesecurity/trufflehog
  dependency-version: 3.95.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-29 09:37:35 +00:00

355 lines
15 KiB
YAML

# SPDX-License-Identifier: AGPL-3.0-only
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved.
# Mac counterpart to studio-ui-smoke.yml. Same Playwright + Chromium
# end-to-end chat UI flow, but on macos-14 (M1) so we catch
# Mac-specific frontend / backend wiring regressions that the Linux
# job would miss (e.g. the Mac Tauri shell loading the same React
# bundle, or the Mac llama.cpp prebuilt's HTTP layer behaving
# differently from the Linux build).
name: Mac Studio UI CI
on:
pull_request:
paths:
- 'studio/**'
- 'unsloth/**'
- 'unsloth_cli/**'
- 'install.sh'
- 'pyproject.toml'
- 'tests/studio/**'
- '.github/scripts/run-studio-permission-browser.sh'
- '.github/workflows/studio-mac-ui-smoke.yml'
push:
branches: [main, pip]
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
ui-smoke:
name: Chat UI Tests
runs-on: macos-14
timeout-minutes: 35
env:
GGUF_REPO: unsloth/gemma-3-270m-it-GGUF
GGUF_VARIANT: UD-Q4_K_XL
GGUF_FILE: gemma-3-270m-it-UD-Q4_K_XL.gguf
STUDIO_PORT: '18896'
HF_HOME: ${{ github.workspace }}/hf-cache
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22'
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.12'
cache: 'pip'
- name: Restore HF_HOME for ${{ env.GGUF_REPO }}
id: cache-hf
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
continue-on-error: true
with:
path: hf-cache
key: ${{ runner.os }}-hf-${{ env.GGUF_REPO }}-${{ env.GGUF_VARIANT }}-v2
- name: Prime HF_HOME with the GGUF
id: prime-hf
if: steps.cache-hf.outputs.cache-hit != 'true' || steps.cache-hf.outcome != 'success'
env:
# Withheld on PR: this step runs checked-out PR code; public GGUF still downloads.
HF_TOKEN: ${{ github.event_name != 'pull_request' && secrets.HF_TOKEN || '' }}
run: |
python -m pip install --upgrade huggingface_hub
mkdir -p hf-cache
bash .github/scripts/hf-download-with-retry.sh "$GGUF_REPO" "$GGUF_FILE"
bash .github/scripts/hf-download-with-retry.sh ggml-org/models tinyllamas/stories260K.gguf
- name: Save HF_HOME for ${{ env.GGUF_REPO }}
if: always() && steps.prime-hf.outcome == 'success'
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: hf-cache
key: ${{ runner.os }}-hf-${{ env.GGUF_REPO }}-${{ env.GGUF_VARIANT }}-v2
- name: Install Unsloth (--local, --no-torch)
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Withheld on PR: this step runs checked-out PR code; public GGUF still downloads.
HF_TOKEN: ${{ github.event_name != 'pull_request' && secrets.HF_TOKEN || '' }}
run: |
mkdir -p logs
set -o pipefail
bash install.sh --local --no-torch 2>&1 | tee logs/install.log
- name: Assert llama.cpp loads on this macOS
run: bash .github/scripts/assert-llama-loads.sh
- name: Install Playwright browsers
# No --with-deps on Mac: that flag installs Linux apt packages.
# GitHub-hosted macos-14 ships the system frameworks Chromium
# needs already.
# Pinned <1.58 because all 1.55-1.58 drivers ship Node 24 on
# macos-14 and intermittently hit 'SyntaxError: Unexpected end
# of JSON input' in pipeTransport.js. Run 25491698868 showed
# the crash hitting 100% of three retry attempts -- not a
# rare race but a hard reproduction. Belt-and-suspenders fix:
# the test scripts pass --single-process to Chromium (see
# tests/studio/playwright_chat_ui.py) AND we patch
# pipeTransport.js below to swallow JSON parse errors instead
# of crashing the driver Node process. Both together let the
# in-script retry recover from any residual flakes.
run: |
pip install 'playwright>=1.55,<1.58'
python -m playwright install chromium webkit
- name: Patch Playwright pipeTransport.js to tolerate malformed JSON
# In Playwright 1.55-1.58, pipeTransport.js does
# `JSON.parse(message)` with no try/catch; when Chromium dies
# mid-write the partial buffer crashes the driver Node
# process and the test script exits with 'Connection closed
# while reading from the driver'. Newer Playwright versions
# added a try/catch upstream. Backport that here.
run: |
python - <<'PY'
import os, re, sys
import playwright
driver_dir = os.path.join(os.path.dirname(playwright.__file__), "driver", "package", "lib", "server")
path = os.path.join(driver_dir, "pipeTransport.js")
src = open(path).read()
# Wrap both `this.onmessage.call(null, JSON.parse(...))` sites in try/catch.
patched = re.sub(
r"this\.onmessage\.call\(null, JSON\.parse\((message2?)\)\);",
r"try { this.onmessage.call(null, JSON.parse(\1)); } "
r"catch (e) { /* swallow malformed JSON from a crashing browser */ }",
src,
)
if patched == src:
# Already patched, or upstream changed -- either way, don't fail the build.
print(f"pipeTransport.js: no JSON.parse calls matched at {path}; skipping.")
else:
open(path, "w").write(patched)
print(f"pipeTransport.js: patched JSON.parse calls in {path}")
PY
- name: Reset auth + boot Unsloth
run: |
# Wipe (not reset-password): the boot below must re-seed a fresh .bootstrap_password.
rm -rf ~/.unsloth/studio/auth
mkdir -p logs
UNSLOTH_API_ONLY=1 unsloth studio -H 127.0.0.1 -p "$STUDIO_PORT" \
> logs/studio.log 2>&1 &
echo "STUDIO_PID=$!" >> "$GITHUB_ENV"
- name: Wait for /api/health
run: |
for i in $(seq 1 180); do
if curl -fs "http://127.0.0.1:${STUDIO_PORT}/api/health" > /tmp/health.json; then
jq -e '.status == "healthy"' /tmp/health.json && break
fi
sleep 1
done
jq -e '.status == "healthy"' /tmp/health.json
- name: Pass bootstrap password to the Playwright step
run: |
OLD=$(cat ~/.unsloth/studio/auth/.bootstrap_password)
NEW="CIUi-$(python -c 'import secrets; print(secrets.token_urlsafe(16))')"
NEW2="CIUi-$(python -c 'import secrets; print(secrets.token_urlsafe(16))')"
echo "::add-mask::$OLD"
echo "::add-mask::$NEW"
echo "::add-mask::$NEW2"
echo "STUDIO_OLD_PW=$OLD" >> "$GITHUB_ENV"
echo "STUDIO_NEW_PW=$NEW" >> "$GITHUB_ENV"
echo "STUDIO_NEW2_PW=$NEW2" >> "$GITHUB_ENV"
- name: Drive the chat UI with Playwright
env:
BASE_URL: http://127.0.0.1:18896
PW_ART_DIR: logs/playwright
STUDIO_UI_STRICT: '1'
# macos-14 free runner is 3 vCPU / 7 GB / no Metal-accel
# available to llama.cpp from CI; gemma-3-270m turn latency
# has been observed to crowd the 180s default. Triple it.
STUDIO_UI_TURN_TIMEOUT_MS: '540000'
# Retry up to 3 times to absorb known macos-14 free-runner
# flakes: (1) Playwright Node 24 pipeTransport.js 'Unexpected
# end of JSON input' crash when the Chromium browser process
# dies mid-test, (2) Chromium net::ERR_NO_BUFFER_SPACE when the
# runner's kernel briefly runs out of socket buffers, and (3) a
# goto 'interrupted by another navigation' when the SPA auth
# guard redirects mid-navigation. The retry FULLY resets Unsloth
# (kill, wipe auth, reboot, wait /api/health, re-export
# bootstrap pw) before re-running the script. A real test failure
# (assertion / timeout) does NOT match any pattern so it bypasses
# retry and surfaces immediately.
run: |
mkdir -p logs/playwright
attempt=1
max_attempts=3
while : ; do
set +e
python tests/studio/playwright_chat_ui.py 2>&1 | tee logs/playwright_attempt_${attempt}.log
rc=${PIPESTATUS[0]}
set -e
if [ "$rc" -eq 0 ]; then
break
fi
if { grep -q "Unexpected end of JSON input" logs/playwright_attempt_${attempt}.log \
|| grep -q "ERR_NO_BUFFER_SPACE" logs/playwright_attempt_${attempt}.log \
|| grep -q "interrupted by another navigation" logs/playwright_attempt_${attempt}.log; } \
&& [ "$attempt" -lt "$max_attempts" ]; then
echo "::warning::Playwright flake on attempt ${attempt}; resetting Unsloth and retrying..."
kill "${STUDIO_PID}" 2>/dev/null || true
sleep 2
rm -rf ~/.unsloth/studio/auth
UNSLOTH_API_ONLY=1 unsloth studio -H 127.0.0.1 -p "$STUDIO_PORT" \
> "logs/studio_retry_${attempt}.log" 2>&1 &
STUDIO_PID=$!
echo "STUDIO_PID=$STUDIO_PID" >> "$GITHUB_ENV"
for i in $(seq 1 180); do
if curl -fs "http://127.0.0.1:${STUDIO_PORT}/api/health" > /tmp/health.json \
&& jq -e '.status == "healthy"' /tmp/health.json >/dev/null; then
break
fi
sleep 1
done
STUDIO_OLD_PW=$(cat ~/.unsloth/studio/auth/.bootstrap_password)
STUDIO_NEW_PW="CIUi-$(python -c 'import secrets; print(secrets.token_urlsafe(16))')"
STUDIO_NEW2_PW="CIUi-$(python -c 'import secrets; print(secrets.token_urlsafe(16))')"
echo "::add-mask::$STUDIO_OLD_PW"
echo "::add-mask::$STUDIO_NEW_PW"
echo "::add-mask::$STUDIO_NEW2_PW"
export STUDIO_OLD_PW STUDIO_NEW_PW STUDIO_NEW2_PW
attempt=$((attempt + 1))
sleep 3
continue
fi
exit "$rc"
done
- name: Stop Unsloth (chat-ui ends with Shutdown click; this is belt-and-suspenders)
if: always()
run: |
kill "${STUDIO_PID}" 2>/dev/null || true
sleep 2
- name: Cross-browser permission controls
run: |
bash .github/scripts/run-studio-permission-browser.sh 18895 webkit
- name: Reset auth + boot Unsloth for extra UI tests (port 18897)
run: |
rm -rf ~/.unsloth/studio/auth
mkdir -p logs
UNSLOTH_API_ONLY=1 unsloth studio -H 127.0.0.1 -p 18897 \
> logs/studio_extra.log 2>&1 &
echo "STUDIO_EXTRA_PID=$!" >> "$GITHUB_ENV"
- name: Wait for /api/health on 18897
run: |
for i in $(seq 1 180); do
if curl -fs "http://127.0.0.1:18897/api/health" > /tmp/health2.json; then
jq -e '.status == "healthy"' /tmp/health2.json && break
fi
sleep 1
done
jq -e '.status == "healthy"' /tmp/health2.json
- name: Pass bootstrap pw for extra UI test
run: |
OLD=$(cat ~/.unsloth/studio/auth/.bootstrap_password)
NEW="CIUiExtra-$(python -c 'import secrets; print(secrets.token_urlsafe(16))')"
echo "::add-mask::$OLD"
echo "::add-mask::$NEW"
echo "STUDIO_EXTRA_OLD_PW=$OLD" >> "$GITHUB_ENV"
echo "STUDIO_EXTRA_NEW_PW=$NEW" >> "$GITHUB_ENV"
- name: Drive Compare/Recipes/Export/Unsloth/Settings with Playwright
env:
BASE_URL: http://127.0.0.1:18897
STUDIO_OLD_PW: ${{ env.STUDIO_EXTRA_OLD_PW }}
STUDIO_NEW_PW: ${{ env.STUDIO_EXTRA_NEW_PW }}
PW_ART_DIR: logs/playwright_extra
STUDIO_UI_STRICT: '1'
# See "Drive the chat UI" step.
STUDIO_UI_TURN_TIMEOUT_MS: '540000'
GGUF_REPO: ${{ env.GGUF_REPO }}
GGUF_VARIANT: ${{ env.GGUF_VARIANT }}
# Same flake-retry shape as "Drive the chat UI with Playwright" -- catches
# pipeTransport JSON crash, ERR_NO_BUFFER_SPACE, and nav interrupts.
run: |
mkdir -p logs/playwright_extra
attempt=1
max_attempts=3
while : ; do
set +e
python tests/studio/playwright_extra_ui.py 2>&1 | tee logs/playwright_extra_attempt_${attempt}.log
rc=${PIPESTATUS[0]}
set -e
if [ "$rc" -eq 0 ]; then
break
fi
if { grep -q "Unexpected end of JSON input" logs/playwright_extra_attempt_${attempt}.log \
|| grep -q "ERR_NO_BUFFER_SPACE" logs/playwright_extra_attempt_${attempt}.log \
|| grep -q "interrupted by another navigation" logs/playwright_extra_attempt_${attempt}.log; } \
&& [ "$attempt" -lt "$max_attempts" ]; then
echo "::warning::Playwright flake on attempt ${attempt}; resetting Unsloth and retrying..."
kill "${STUDIO_EXTRA_PID}" 2>/dev/null || true
sleep 2
rm -rf ~/.unsloth/studio/auth
UNSLOTH_API_ONLY=1 unsloth studio -H 127.0.0.1 -p 18897 \
> "logs/studio_extra_retry_${attempt}.log" 2>&1 &
STUDIO_EXTRA_PID=$!
echo "STUDIO_EXTRA_PID=$STUDIO_EXTRA_PID" >> "$GITHUB_ENV"
for i in $(seq 1 180); do
if curl -fs "http://127.0.0.1:18897/api/health" > /tmp/health2.json \
&& jq -e '.status == "healthy"' /tmp/health2.json >/dev/null; then
break
fi
sleep 1
done
STUDIO_OLD_PW=$(cat ~/.unsloth/studio/auth/.bootstrap_password)
STUDIO_NEW_PW="CIUiExtra-$(python -c 'import secrets; print(secrets.token_urlsafe(16))')"
echo "::add-mask::$STUDIO_OLD_PW"
echo "::add-mask::$STUDIO_NEW_PW"
export STUDIO_OLD_PW STUDIO_NEW_PW
attempt=$((attempt + 1))
sleep 3
continue
fi
exit "$rc"
done
- name: Stop second Unsloth
if: always()
run: |
kill "${STUDIO_EXTRA_PID}" 2>/dev/null || true
sleep 2
- name: Upload Playwright artifacts
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: mac-studio-ui-smoke-artifacts
path: |
logs/studio.log
logs/studio_extra.log
logs/install.log
logs/playwright
logs/playwright-permissions-*
logs/playwright_extra
logs/studio-permissions-*.log
retention-days: 7