Skip to content

fix: skip restart request for named sessions on self-handoff #2745

fix: skip restart request for named sessions on self-handoff

fix: skip restart request for named sessions on self-handoff #2745

Workflow file for this run

name: CI
on:
push:
branches: [main]
pull_request:
permissions:
contents: read
jobs:
# Detect which paths changed to gate conditional jobs.
changes:
name: Detect changes
runs-on: ubuntu-latest
outputs:
mail: ${{ steps.filter.outputs.mail }}
docker: ${{ steps.filter.outputs.docker }}
k8s: ${{ steps.filter.outputs.k8s }}
beads: ${{ steps.filter.outputs.beads }}
packs: ${{ steps.filter.outputs.packs }}
worker: ${{ steps.filter.outputs.worker }}
worker_phase2: ${{ steps.filter.outputs.worker_phase2 }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3
id: filter
with:
filters: |
mail:
- 'internal/mail/**'
- 'contrib/mail-scripts/**'
docker:
- 'internal/session/**'
- 'scripts/gc-session-docker'
- 'scripts/test-docker-session'
- 'contrib/session-scripts/**'
k8s:
- 'internal/session/**'
- 'contrib/session-scripts/gc-session-k8s*'
- 'test/integration/session_k8s_test.go'
beads:
- 'go.mod'
- 'internal/beads/**'
packs:
- 'examples/gastown/packs/**'
- 'internal/config/pack.go'
- 'internal/config/compose.go'
- 'cmd/gc/embed_builtin_packs.go'
worker:
- 'go.mod'
- 'go.sum'
- '.github/workflows/**'
- 'Makefile'
- 'internal/worker/**'
- 'internal/sessionlog/**'
- 'internal/runtime/**'
- 'internal/config/**'
- 'cmd/gc/template_resolve*.go'
- 'cmd/gc/session_*'
- 'test/**worker**'
worker_phase2:
- 'go.mod'
- 'go.sum'
- '.github/workflows/**'
- 'Makefile'
- 'internal/worker/**'
- 'internal/sessionlog/**'
- 'internal/runtime/**'
- 'internal/config/**'
- 'cmd/gc/**'
# Always runs: lint, fmt, vet, unit tests, docs, acceptance, coverage.
check:
name: Check
runs-on: ubuntu-latest
env:
# Pinned dependency versions — keep in sync with deps.env.
DOLT_VERSION: "1.86.1"
BD_VERSION: "v1.0.0"
# Make TestGeneratedClientInSync fatal on missing oapi-codegen so the
# spec→client drift check can never silently skip in CI.
GC_REQUIRE_OAPI_CODEGEN: "1"
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
with:
go-version: "1.25.8"
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: "22"
- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y tmux jq
- name: Install dolt v${{ env.DOLT_VERSION }}
run: |
curl -fsSL https://github.com/dolthub/dolt/releases/download/v${{ env.DOLT_VERSION }}/install.sh | sudo bash
dolt version
- name: Install released bd v${{ env.BD_VERSION }}
run: |
archive="beads_${BD_VERSION#v}_linux_amd64.tar.gz"
mkdir -p "$RUNNER_TEMP/beads"
curl -fsSL -o "$RUNNER_TEMP/$archive" \
"https://github.com/gastownhall/beads/releases/download/${BD_VERSION}/${archive}"
tar -xzf "$RUNNER_TEMP/$archive" -C "$RUNNER_TEMP/beads" bd
sudo install -m 0755 "$RUNNER_TEMP/beads/bd" /usr/local/bin/bd
bd version
- name: Install Claude CLI
run: npm install -g @anthropic-ai/claude-code
- name: Install tools
run: make install-tools
- name: Lint
run: make lint
- name: Format
run: make fmt-check
- name: Vet
run: make vet
- name: Test
run: make test-cover
- name: Docs
run: make check-docs
- name: Acceptance tests (Tier A)
run: make test-acceptance
- name: Dashboard bundle drift check
run: make dashboard-ci
- name: OpenAPI spec + client drift check
run: make spec-ci
- name: Upload coverage to Codecov
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5
with:
files: coverage.txt
token: ${{ secrets.CODECOV_TOKEN }}
verbose: true
# Runs always, but remains non-blocking while integration/provider paths are still stabilizing.
integration-shards:
name: Integration / ${{ matrix.shard_name }}
runs-on: ubuntu-latest
continue-on-error: true
timeout-minutes: ${{ matrix.timeout_minutes }}
strategy:
fail-fast: false
matrix:
include:
- shard_name: packages
timeout_minutes: 35
command: make test-integration-packages
- shard_name: review-formulas
timeout_minutes: 45
command: make test-integration-review-formulas
- shard_name: bdstore
timeout_minutes: 15
command: make test-integration-bdstore
- shard_name: rest
timeout_minutes: 35
command: make test-integration-rest
env:
DOLT_VERSION: "1.86.1"
BD_VERSION: "v1.0.0"
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: ./.github/actions/setup-gascity-ubuntu
with:
dolt-version: ${{ env.DOLT_VERSION }}
bd-version: ${{ env.BD_VERSION }}
install-claude-cli: "true"
- name: Install tools
run: make install-tools
- name: Run integration shard
run: ${{ matrix.command }}
worker-core-claude:
name: Worker core (Claude)
needs: changes
if: needs.changes.outputs.worker == 'true'
runs-on: ubuntu-latest
env:
PROFILE: claude/tmux-cli
WORKER_REPORT_DIR: /tmp/worker-core-claude-reports
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
with:
go-version-file: go.mod
- name: Prepare worker report dir
run: mkdir -p "$WORKER_REPORT_DIR"
- name: WorkerCore transcript/continuation conformance
id: worker_core_tests
run: GC_WORKER_REPORT_DIR="$WORKER_REPORT_DIR" make test-worker-core PROFILE="$PROFILE"
- name: Ensure WorkerCore reports
if: ${{ always() && steps.worker_core_tests.outcome != 'success' }}
run: python3 .github/workflows/scripts/worker_report_stub.py "$WORKER_REPORT_DIR" "worker-core"
- name: WorkerCore report summary
if: ${{ always() }}
run: python3 .github/workflows/scripts/worker_report_summary.py "$WORKER_REPORT_DIR"
- name: Upload WorkerCore reports
if: ${{ always() }}
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: worker-core-claude-reports
path: ${{ env.WORKER_REPORT_DIR }}/*.json
if-no-files-found: error
worker-core-codex:
name: Worker core (Codex)
needs: changes
if: needs.changes.outputs.worker == 'true'
runs-on: ubuntu-latest
env:
PROFILE: codex/tmux-cli
WORKER_REPORT_DIR: /tmp/worker-core-codex-reports
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
with:
go-version-file: go.mod
- name: Prepare worker report dir
run: mkdir -p "$WORKER_REPORT_DIR"
- name: WorkerCore transcript/continuation conformance
id: worker_core_tests
run: GC_WORKER_REPORT_DIR="$WORKER_REPORT_DIR" make test-worker-core PROFILE="$PROFILE"
- name: Ensure WorkerCore reports
if: ${{ always() && steps.worker_core_tests.outcome != 'success' }}
run: python3 .github/workflows/scripts/worker_report_stub.py "$WORKER_REPORT_DIR" "worker-core"
- name: WorkerCore report summary
if: ${{ always() }}
run: python3 .github/workflows/scripts/worker_report_summary.py "$WORKER_REPORT_DIR"
- name: Upload WorkerCore reports
if: ${{ always() }}
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: worker-core-codex-reports
path: ${{ env.WORKER_REPORT_DIR }}/*.json
if-no-files-found: error
worker-core-gemini:
name: Worker core (Gemini)
needs: changes
if: needs.changes.outputs.worker == 'true'
runs-on: ubuntu-latest
env:
PROFILE: gemini/tmux-cli
WORKER_REPORT_DIR: /tmp/worker-core-gemini-reports
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
with:
go-version-file: go.mod
- name: Prepare worker report dir
run: mkdir -p "$WORKER_REPORT_DIR"
- name: WorkerCore transcript/continuation conformance
id: worker_core_tests
run: GC_WORKER_REPORT_DIR="$WORKER_REPORT_DIR" make test-worker-core PROFILE="$PROFILE"
- name: Ensure WorkerCore reports
if: ${{ always() && steps.worker_core_tests.outcome != 'success' }}
run: python3 .github/workflows/scripts/worker_report_stub.py "$WORKER_REPORT_DIR" "worker-core"
- name: WorkerCore report summary
if: ${{ always() }}
run: python3 .github/workflows/scripts/worker_report_summary.py "$WORKER_REPORT_DIR"
- name: Upload WorkerCore reports
if: ${{ always() }}
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: worker-core-gemini-reports
path: ${{ env.WORKER_REPORT_DIR }}/*.json
if-no-files-found: error
worker-core-summary:
name: Worker core summary
needs:
- changes
- worker-core-claude
- worker-core-codex
- worker-core-gemini
if: ${{ always() }}
runs-on: ubuntu-latest
env:
WORKER_ROLLUP_DIR: /tmp/worker-core-summary-reports
WORKER_ROLLUP_JSON: /tmp/worker-core-summary-reports/worker-core-summary.json
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Prepare worker rollup dir
if: ${{ needs.changes.outputs.worker == 'true' }}
run: mkdir -p "$WORKER_ROLLUP_DIR"
- name: Download WorkerCore Claude reports
id: download_worker_core_claude
if: ${{ needs.changes.outputs.worker == 'true' }}
continue-on-error: true
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: worker-core-claude-reports
path: ${{ env.WORKER_ROLLUP_DIR }}/claude
- name: Download WorkerCore Codex reports
id: download_worker_core_codex
if: ${{ needs.changes.outputs.worker == 'true' }}
continue-on-error: true
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: worker-core-codex-reports
path: ${{ env.WORKER_ROLLUP_DIR }}/codex
- name: Download WorkerCore Gemini reports
id: download_worker_core_gemini
if: ${{ needs.changes.outputs.worker == 'true' }}
continue-on-error: true
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: worker-core-gemini-reports
path: ${{ env.WORKER_ROLLUP_DIR }}/gemini
- name: WorkerCore rollup summary
if: ${{ needs.changes.outputs.worker == 'true' }}
env:
CLAUDE_DOWNLOAD: ${{ steps.download_worker_core_claude.outcome }}
CODEX_DOWNLOAD: ${{ steps.download_worker_core_codex.outcome }}
GEMINI_DOWNLOAD: ${{ steps.download_worker_core_gemini.outcome }}
run: |
python3 .github/workflows/scripts/worker_report_rollup.py \
"$WORKER_ROLLUP_DIR" \
--title "Worker core summary" \
--require-reports \
--expected-profile "claude/tmux-cli=$CLAUDE_DOWNLOAD" \
--expected-profile "codex/tmux-cli=$CODEX_DOWNLOAD" \
--expected-profile "gemini/tmux-cli=$GEMINI_DOWNLOAD" \
--output "$WORKER_ROLLUP_JSON"
- name: Upload WorkerCore rollup
if: ${{ always() && needs.changes.outputs.worker == 'true' }}
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: worker-core-summary-reports
path: ${{ env.WORKER_ROLLUP_JSON }}
if-no-files-found: error
- name: Assert worker-core profile matrix passed
run: |
CHANGES_RESULT='${{ needs.changes.result }}'
CHANGED='${{ needs.changes.outputs.worker }}'
CLAUDE_RESULT='${{ needs.worker-core-claude.result }}'
CODEX_RESULT='${{ needs.worker-core-codex.result }}'
GEMINI_RESULT='${{ needs.worker-core-gemini.result }}'
CLAUDE_DOWNLOAD='${{ steps.download_worker_core_claude.outcome }}'
CODEX_DOWNLOAD='${{ steps.download_worker_core_codex.outcome }}'
GEMINI_DOWNLOAD='${{ steps.download_worker_core_gemini.outcome }}'
if [ -f "$WORKER_ROLLUP_JSON" ]; then
ROLLUP_STATUS="$(python3 -c "import json, sys; print(json.load(open(sys.argv[1], encoding='utf-8')).get('summary', {}).get('status', 'unknown'))" "$WORKER_ROLLUP_JSON")"
else
ROLLUP_STATUS='missing'
fi
printf 'changes-result=%s\n' "$CHANGES_RESULT"
printf 'worker-changes=%s\n' "$CHANGED"
printf 'worker-core-claude=%s\n' "$CLAUDE_RESULT"
printf 'worker-core-codex=%s\n' "$CODEX_RESULT"
printf 'worker-core-gemini=%s\n' "$GEMINI_RESULT"
printf 'download-worker-core-claude=%s\n' "$CLAUDE_DOWNLOAD"
printf 'download-worker-core-codex=%s\n' "$CODEX_DOWNLOAD"
printf 'download-worker-core-gemini=%s\n' "$GEMINI_DOWNLOAD"
printf 'worker-core-rollup=%s\n' "$ROLLUP_STATUS"
if [ "$CHANGES_RESULT" != "success" ]; then
echo "changes job did not complete successfully" >&2
exit 1
fi
if [ "$CHANGED" != "true" ]; then
echo "No worker changes detected; passing summary without fan-in."
exit 0
fi
if [ "$CLAUDE_DOWNLOAD" != "success" ] || [ "$CODEX_DOWNLOAD" != "success" ] || [ "$GEMINI_DOWNLOAD" != "success" ]; then
echo "worker-core summary is missing one or more expected profile artifacts" >&2
exit 1
fi
if [ "$ROLLUP_STATUS" != "pass" ]; then
echo "worker-core rollup reported a non-pass status" >&2
exit 1
fi
if [ "$CLAUDE_RESULT" != "success" ] || [ "$CODEX_RESULT" != "success" ] || [ "$GEMINI_RESULT" != "success" ]; then
echo "worker-core matrix failed" >&2
exit 1
fi
worker-core-phase2-claude:
name: Worker core phase 2 (Claude)
needs: changes
if: needs.changes.outputs.worker_phase2 == 'true'
runs-on: ubuntu-latest
env:
PROFILE: claude/tmux-cli
WORKER_REPORT_DIR: /tmp/worker-core-phase2-claude-reports
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
with:
go-version-file: go.mod
- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y tmux
- name: Prepare worker report dir
run: mkdir -p "$WORKER_REPORT_DIR"
- name: WorkerCore phase-2 conformance
id: worker_core_phase2_tests
run: |
GC_WORKER_REPORT_DIR="$WORKER_REPORT_DIR" make test-worker-core-phase2 PROFILE="$PROFILE"
GC_WORKER_REPORT_DIR="$WORKER_REPORT_DIR" make test-worker-core-phase2-real-transport PROFILE="$PROFILE"
- name: Ensure WorkerCore phase-2 reports
if: ${{ always() && steps.worker_core_phase2_tests.outcome != 'success' }}
run: python3 .github/workflows/scripts/worker_report_stub.py "$WORKER_REPORT_DIR" "worker-core-phase2"
- name: WorkerCore phase-2 report summary
if: ${{ always() }}
run: python3 .github/workflows/scripts/worker_report_summary.py "$WORKER_REPORT_DIR"
- name: Upload WorkerCore phase-2 reports
if: ${{ always() }}
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: worker-core-phase2-claude-reports
path: ${{ env.WORKER_REPORT_DIR }}/*.json
if-no-files-found: error
worker-core-phase2-codex:
name: Worker core phase 2 (Codex)
needs: changes
if: needs.changes.outputs.worker_phase2 == 'true'
runs-on: ubuntu-latest
env:
PROFILE: codex/tmux-cli
WORKER_REPORT_DIR: /tmp/worker-core-phase2-codex-reports
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
with:
go-version-file: go.mod
- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y tmux
- name: Prepare worker report dir
run: mkdir -p "$WORKER_REPORT_DIR"
- name: WorkerCore phase-2 conformance
id: worker_core_phase2_tests
run: |
GC_WORKER_REPORT_DIR="$WORKER_REPORT_DIR" make test-worker-core-phase2 PROFILE="$PROFILE"
GC_WORKER_REPORT_DIR="$WORKER_REPORT_DIR" make test-worker-core-phase2-real-transport PROFILE="$PROFILE"
- name: Ensure WorkerCore phase-2 reports
if: ${{ always() && steps.worker_core_phase2_tests.outcome != 'success' }}
run: python3 .github/workflows/scripts/worker_report_stub.py "$WORKER_REPORT_DIR" "worker-core-phase2"
- name: WorkerCore phase-2 report summary
if: ${{ always() }}
run: python3 .github/workflows/scripts/worker_report_summary.py "$WORKER_REPORT_DIR"
- name: Upload WorkerCore phase-2 reports
if: ${{ always() }}
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: worker-core-phase2-codex-reports
path: ${{ env.WORKER_REPORT_DIR }}/*.json
if-no-files-found: error
worker-core-phase2-gemini:
name: Worker core phase 2 (Gemini)
needs: changes
if: needs.changes.outputs.worker_phase2 == 'true'
runs-on: ubuntu-latest
env:
PROFILE: gemini/tmux-cli
WORKER_REPORT_DIR: /tmp/worker-core-phase2-gemini-reports
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
with:
go-version-file: go.mod
- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y tmux
- name: Prepare worker report dir
run: mkdir -p "$WORKER_REPORT_DIR"
- name: WorkerCore phase-2 conformance
id: worker_core_phase2_tests
run: |
GC_WORKER_REPORT_DIR="$WORKER_REPORT_DIR" make test-worker-core-phase2 PROFILE="$PROFILE"
GC_WORKER_REPORT_DIR="$WORKER_REPORT_DIR" make test-worker-core-phase2-real-transport PROFILE="$PROFILE"
- name: Ensure WorkerCore phase-2 reports
if: ${{ always() && steps.worker_core_phase2_tests.outcome != 'success' }}
run: python3 .github/workflows/scripts/worker_report_stub.py "$WORKER_REPORT_DIR" "worker-core-phase2"
- name: WorkerCore phase-2 report summary
if: ${{ always() }}
run: python3 .github/workflows/scripts/worker_report_summary.py "$WORKER_REPORT_DIR"
- name: Upload WorkerCore phase-2 reports
if: ${{ always() }}
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: worker-core-phase2-gemini-reports
path: ${{ env.WORKER_REPORT_DIR }}/*.json
if-no-files-found: error
worker-core-phase2-summary:
name: Worker core phase 2 summary
needs:
- changes
- worker-core-phase2-claude
- worker-core-phase2-codex
- worker-core-phase2-gemini
if: ${{ always() }}
runs-on: ubuntu-latest
env:
WORKER_ROLLUP_DIR: /tmp/worker-core-phase2-summary-reports
WORKER_ROLLUP_JSON: /tmp/worker-core-phase2-summary-reports/worker-core-phase2-summary.json
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Prepare worker rollup dir
if: ${{ needs.changes.outputs.worker_phase2 == 'true' }}
run: mkdir -p "$WORKER_ROLLUP_DIR"
- name: Download WorkerCore phase-2 Claude reports
id: download_worker_core_phase2_claude
if: ${{ needs.changes.outputs.worker_phase2 == 'true' }}
continue-on-error: true
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: worker-core-phase2-claude-reports
path: ${{ env.WORKER_ROLLUP_DIR }}/claude
- name: Download WorkerCore phase-2 Codex reports
id: download_worker_core_phase2_codex
if: ${{ needs.changes.outputs.worker_phase2 == 'true' }}
continue-on-error: true
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: worker-core-phase2-codex-reports
path: ${{ env.WORKER_ROLLUP_DIR }}/codex
- name: Download WorkerCore phase-2 Gemini reports
id: download_worker_core_phase2_gemini
if: ${{ needs.changes.outputs.worker_phase2 == 'true' }}
continue-on-error: true
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: worker-core-phase2-gemini-reports
path: ${{ env.WORKER_ROLLUP_DIR }}/gemini
- name: WorkerCore phase-2 rollup summary
if: ${{ needs.changes.outputs.worker_phase2 == 'true' }}
env:
CLAUDE_DOWNLOAD: ${{ steps.download_worker_core_phase2_claude.outcome }}
CODEX_DOWNLOAD: ${{ steps.download_worker_core_phase2_codex.outcome }}
GEMINI_DOWNLOAD: ${{ steps.download_worker_core_phase2_gemini.outcome }}
run: |
python3 .github/workflows/scripts/worker_report_rollup.py \
"$WORKER_ROLLUP_DIR" \
--title "Worker core phase 2 summary" \
--require-reports \
--expected-profile "claude/tmux-cli=$CLAUDE_DOWNLOAD" \
--expected-profile "codex/tmux-cli=$CODEX_DOWNLOAD" \
--expected-profile "gemini/tmux-cli=$GEMINI_DOWNLOAD" \
--output "$WORKER_ROLLUP_JSON"
- name: Upload WorkerCore phase-2 rollup
if: ${{ always() && needs.changes.outputs.worker_phase2 == 'true' }}
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: worker-core-phase2-summary-reports
path: ${{ env.WORKER_ROLLUP_JSON }}
if-no-files-found: error
- name: Assert worker-core phase-2 matrix passed
run: |
CHANGES_RESULT='${{ needs.changes.result }}'
CHANGED='${{ needs.changes.outputs.worker_phase2 }}'
CLAUDE_RESULT='${{ needs.worker-core-phase2-claude.result }}'
CODEX_RESULT='${{ needs.worker-core-phase2-codex.result }}'
GEMINI_RESULT='${{ needs.worker-core-phase2-gemini.result }}'
CLAUDE_DOWNLOAD='${{ steps.download_worker_core_phase2_claude.outcome }}'
CODEX_DOWNLOAD='${{ steps.download_worker_core_phase2_codex.outcome }}'
GEMINI_DOWNLOAD='${{ steps.download_worker_core_phase2_gemini.outcome }}'
if [ -f "$WORKER_ROLLUP_JSON" ]; then
ROLLUP_STATUS="$(python3 -c "import json, sys; print(json.load(open(sys.argv[1], encoding='utf-8')).get('summary', {}).get('status', 'unknown'))" "$WORKER_ROLLUP_JSON")"
else
ROLLUP_STATUS='missing'
fi
printf 'changes-result=%s\n' "$CHANGES_RESULT"
printf 'worker-phase2-changes=%s\n' "$CHANGED"
printf 'worker-core-phase2-claude=%s\n' "$CLAUDE_RESULT"
printf 'worker-core-phase2-codex=%s\n' "$CODEX_RESULT"
printf 'worker-core-phase2-gemini=%s\n' "$GEMINI_RESULT"
printf 'download-worker-core-phase2-claude=%s\n' "$CLAUDE_DOWNLOAD"
printf 'download-worker-core-phase2-codex=%s\n' "$CODEX_DOWNLOAD"
printf 'download-worker-core-phase2-gemini=%s\n' "$GEMINI_DOWNLOAD"
printf 'worker-core-phase2-rollup=%s\n' "$ROLLUP_STATUS"
if [ "$CHANGES_RESULT" != "success" ]; then
echo "changes job did not complete successfully" >&2
exit 1
fi
if [ "$CHANGED" != "true" ]; then
echo "No phase-2 worker changes detected; passing summary without fan-in."
exit 0
fi
if [ "$CLAUDE_DOWNLOAD" != "success" ] || [ "$CODEX_DOWNLOAD" != "success" ] || [ "$GEMINI_DOWNLOAD" != "success" ]; then
echo "worker-core phase-2 summary is missing one or more expected profile artifacts" >&2
exit 1
fi
if [ "$ROLLUP_STATUS" != "pass" ]; then
echo "worker-core phase-2 rollup reported a non-pass status" >&2
exit 1
fi
if [ "$CLAUDE_RESULT" != "success" ] || [ "$CODEX_RESULT" != "success" ] || [ "$GEMINI_RESULT" != "success" ]; then
echo "worker-core phase-2 matrix failed" >&2
exit 1
fi
worker-inference-phase3-claude:
name: Worker inference phase 3 (Claude)
needs: changes
if: needs.changes.outputs.worker == 'true'
runs-on: ubuntu-latest
env:
PROFILE: claude/tmux-cli
WORKER_REPORT_DIR: /tmp/worker-inference-phase3-claude-reports
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Prepare worker report dir
run: mkdir -p "$WORKER_REPORT_DIR"
- name: Emit manual-only WorkerInference phase-3 report
run: python3 .github/workflows/scripts/worker_report_manual_only.py "$WORKER_REPORT_DIR" "worker-inference-phase3"
- name: WorkerInference phase-3 report summary
if: ${{ always() }}
run: python3 .github/workflows/scripts/worker_report_summary.py "$WORKER_REPORT_DIR"
- name: Upload WorkerInference phase-3 reports
if: ${{ always() }}
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: worker-inference-phase3-claude-reports
path: ${{ env.WORKER_REPORT_DIR }}/*.json
if-no-files-found: error
worker-inference-phase3-codex:
name: Worker inference phase 3 (Codex)
needs: changes
if: needs.changes.outputs.worker == 'true'
runs-on: ubuntu-latest
env:
PROFILE: codex/tmux-cli
WORKER_REPORT_DIR: /tmp/worker-inference-phase3-codex-reports
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Prepare worker report dir
run: mkdir -p "$WORKER_REPORT_DIR"
- name: Emit manual-only WorkerInference phase-3 report
run: python3 .github/workflows/scripts/worker_report_manual_only.py "$WORKER_REPORT_DIR" "worker-inference-phase3"
- name: WorkerInference phase-3 report summary
if: ${{ always() }}
run: python3 .github/workflows/scripts/worker_report_summary.py "$WORKER_REPORT_DIR"
- name: Upload WorkerInference phase-3 reports
if: ${{ always() }}
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: worker-inference-phase3-codex-reports
path: ${{ env.WORKER_REPORT_DIR }}/*.json
if-no-files-found: error
worker-inference-phase3-gemini:
name: Worker inference phase 3 (Gemini)
needs: changes
if: needs.changes.outputs.worker == 'true'
runs-on: ubuntu-latest
env:
PROFILE: gemini/tmux-cli
WORKER_REPORT_DIR: /tmp/worker-inference-phase3-gemini-reports
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Prepare worker report dir
run: mkdir -p "$WORKER_REPORT_DIR"
- name: Emit manual-only WorkerInference phase-3 report
run: python3 .github/workflows/scripts/worker_report_manual_only.py "$WORKER_REPORT_DIR" "worker-inference-phase3"
- name: WorkerInference phase-3 report summary
if: ${{ always() }}
run: python3 .github/workflows/scripts/worker_report_summary.py "$WORKER_REPORT_DIR"
- name: Upload WorkerInference phase-3 reports
if: ${{ always() }}
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: worker-inference-phase3-gemini-reports
path: ${{ env.WORKER_REPORT_DIR }}/*.json
if-no-files-found: error
worker-inference-phase3-summary:
name: Worker inference phase 3 summary
needs:
- changes
- worker-inference-phase3-claude
- worker-inference-phase3-codex
- worker-inference-phase3-gemini
if: ${{ always() }}
runs-on: ubuntu-latest
env:
WORKER_ROLLUP_DIR: /tmp/worker-inference-phase3-summary-reports
WORKER_ROLLUP_JSON: /tmp/worker-inference-phase3-summary-reports/worker-inference-phase3-summary.json
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Prepare worker rollup dir
if: ${{ needs.changes.outputs.worker == 'true' }}
run: mkdir -p "$WORKER_ROLLUP_DIR"
- name: Download WorkerInference phase-3 Claude reports
id: download_worker_inference_phase3_claude
if: ${{ needs.changes.outputs.worker == 'true' }}
continue-on-error: true
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: worker-inference-phase3-claude-reports
path: ${{ env.WORKER_ROLLUP_DIR }}/claude
- name: Download WorkerInference phase-3 Codex reports
id: download_worker_inference_phase3_codex
if: ${{ needs.changes.outputs.worker == 'true' }}
continue-on-error: true
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: worker-inference-phase3-codex-reports
path: ${{ env.WORKER_ROLLUP_DIR }}/codex
- name: Download WorkerInference phase-3 Gemini reports
id: download_worker_inference_phase3_gemini
if: ${{ needs.changes.outputs.worker == 'true' }}
continue-on-error: true
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: worker-inference-phase3-gemini-reports
path: ${{ env.WORKER_ROLLUP_DIR }}/gemini
- name: WorkerInference phase-3 rollup summary
if: ${{ needs.changes.outputs.worker == 'true' }}
env:
CLAUDE_DOWNLOAD: ${{ steps.download_worker_inference_phase3_claude.outcome }}
CODEX_DOWNLOAD: ${{ steps.download_worker_inference_phase3_codex.outcome }}
GEMINI_DOWNLOAD: ${{ steps.download_worker_inference_phase3_gemini.outcome }}
run: |
python3 .github/workflows/scripts/worker_report_rollup.py \
"$WORKER_ROLLUP_DIR" \
--title "Worker inference phase 3 summary" \
--require-reports \
--expected-profile "claude/tmux-cli=$CLAUDE_DOWNLOAD" \
--expected-profile "codex/tmux-cli=$CODEX_DOWNLOAD" \
--expected-profile "gemini/tmux-cli=$GEMINI_DOWNLOAD" \
--output "$WORKER_ROLLUP_JSON"
- name: Upload WorkerInference phase-3 rollup
if: ${{ always() && needs.changes.outputs.worker == 'true' }}
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: worker-inference-phase3-summary-reports
path: ${{ env.WORKER_ROLLUP_JSON }}
if-no-files-found: error
- name: Assert worker-inference phase-3 matrix reported
run: |
CHANGES_RESULT='${{ needs.changes.result }}'
CHANGED='${{ needs.changes.outputs.worker }}'
CLAUDE_RESULT='${{ needs.worker-inference-phase3-claude.result }}'
CODEX_RESULT='${{ needs.worker-inference-phase3-codex.result }}'
GEMINI_RESULT='${{ needs.worker-inference-phase3-gemini.result }}'
CLAUDE_DOWNLOAD='${{ steps.download_worker_inference_phase3_claude.outcome }}'
CODEX_DOWNLOAD='${{ steps.download_worker_inference_phase3_codex.outcome }}'
GEMINI_DOWNLOAD='${{ steps.download_worker_inference_phase3_gemini.outcome }}'
if [ -f "$WORKER_ROLLUP_JSON" ]; then
ROLLUP_STATUS="$(python3 -c "import json, sys; print(json.load(open(sys.argv[1], encoding='utf-8')).get('summary', {}).get('status', 'unknown'))" "$WORKER_ROLLUP_JSON")"
else
ROLLUP_STATUS='missing'
fi
printf 'changes-result=%s\n' "$CHANGES_RESULT"
printf 'worker-changes=%s\n' "$CHANGED"
printf 'worker-inference-phase3-claude=%s\n' "$CLAUDE_RESULT"
printf 'worker-inference-phase3-codex=%s\n' "$CODEX_RESULT"
printf 'worker-inference-phase3-gemini=%s\n' "$GEMINI_RESULT"
printf 'download-worker-inference-phase3-claude=%s\n' "$CLAUDE_DOWNLOAD"
printf 'download-worker-inference-phase3-codex=%s\n' "$CODEX_DOWNLOAD"
printf 'download-worker-inference-phase3-gemini=%s\n' "$GEMINI_DOWNLOAD"
printf 'worker-inference-phase3-rollup=%s\n' "$ROLLUP_STATUS"
if [ "$CHANGES_RESULT" != "success" ]; then
echo "changes job did not complete successfully" >&2
exit 1
fi
if [ "$CHANGED" != "true" ]; then
echo "No phase-3 worker changes detected; passing summary without fan-in."
exit 0
fi
if [ "$CLAUDE_DOWNLOAD" != "success" ] || [ "$CODEX_DOWNLOAD" != "success" ] || [ "$GEMINI_DOWNLOAD" != "success" ]; then
echo "worker-inference phase-3 summary is missing one or more expected profile artifacts" >&2
exit 1
fi
if [ "$ROLLUP_STATUS" != "pass" ] && [ "$ROLLUP_STATUS" != "unsupported" ]; then
echo "worker-inference phase-3 rollup reported an unexpected status" >&2
exit 1
fi
if [ "$ROLLUP_STATUS" = "unsupported" ]; then
echo "worker-inference phase-3 is catalog-only until executable inference scenarios land."
fi
if [ "$CLAUDE_RESULT" != "success" ] || [ "$CODEX_RESULT" != "success" ] || [ "$GEMINI_RESULT" != "success" ]; then
echo "worker-inference phase-3 matrix failed" >&2
exit 1
fi
# Runs when pack-related files change — full gastown integration suite.
pack-gate:
name: Pack compatibility gate
needs: [changes, check]
if: needs.changes.outputs.packs == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
with:
go-version: "1.25.8"
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: "22"
- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y tmux jq
- name: Install dolt
run: |
curl -fsSL https://github.com/dolthub/dolt/releases/download/v${{ env.DOLT_VERSION }}/install.sh | sudo bash
- name: Install released bd v${{ env.BD_VERSION }}
run: |
archive="beads_${BD_VERSION#v}_linux_amd64.tar.gz"
mkdir -p "$RUNNER_TEMP/beads"
curl -fsSL -o "$RUNNER_TEMP/$archive" \
"https://github.com/gastownhall/beads/releases/download/${BD_VERSION}/${archive}"
tar -xzf "$RUNNER_TEMP/$archive" -C "$RUNNER_TEMP/beads" bd
sudo install -m 0755 "$RUNNER_TEMP/beads/bd" /usr/local/bin/bd
- name: Install Claude CLI
run: npm install -g @anthropic-ai/claude-code
- name: Install tools
run: make install-tools
- name: Pack compatibility tests
run: make test-acceptance
env:
DOLT_VERSION: "1.86.1"
BD_VERSION: "v1.0.0"
# Dashboard SPA typecheck + tests + build. Runs on every push/PR
# so TS drift against the spec (e.g. a query param tightening from
# string to boolean) fails CI instead of shipping as a silent
# runtime bug. Vite's build transpiles TS to JS and does not fail
# on type errors; `tsc --noEmit` via `npm run typecheck` is the
# load-bearing discipline step.
dashboard:
name: Dashboard SPA
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: "22"
- name: Install SPA dependencies
run: npm install --silent
working-directory: cmd/gc/dashboard/web
- name: Verify generated TS schema is in sync
run: |
npm run gen --silent
if ! git diff --exit-code src/generated/schema.d.ts; then
echo "::error::Generated TS schema drifted. Regenerate via 'npm run gen' in cmd/gc/dashboard/web."
exit 1
fi
working-directory: cmd/gc/dashboard/web
- name: Typecheck (tsc --noEmit)
run: npm run typecheck
working-directory: cmd/gc/dashboard/web
- name: Vitest
run: npm test
working-directory: cmd/gc/dashboard/web
- name: Build
run: npm run build --silent
working-directory: cmd/gc/dashboard/web
# Runs when mail-related source paths change.
mcp-mail:
name: MCP mail conformance
needs: changes
if: needs.changes.outputs.mail == 'true'
runs-on: ubuntu-latest
continue-on-error: true # upstream mcp_agent_mail API may drift
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
with:
go-version-file: go.mod
- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y jq curl
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: '3.12'
- name: Install mcp_agent_mail
run: pip install 'mcp-agent-mail==0.1.0'
- name: MCP mail conformance test
run: make test-mcp-mail
# Runs when session/Docker-related source paths change.
docker-session:
name: Docker session
needs: changes
if: needs.changes.outputs.docker == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
with:
go-version-file: go.mod
- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y jq
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- name: Install tools
run: make install-tools
- name: Docker session tests
run: make test-docker
# Runs when session/K8s-related source paths change.
# Requires K8s CI infrastructure — no-op until secrets are configured.
k8s-session:
name: K8s session
needs: changes
if: needs.changes.outputs.k8s == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
with:
go-version-file: go.mod
- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y jq
- name: K8s session tests
if: env.GC_K8S_AVAILABLE == 'true'
env:
GC_K8S_AVAILABLE: ${{ secrets.GC_K8S_AVAILABLE }}
run: make test-k8s