Skip to content

fix: harden session routing and nested worktrees #3188

fix: harden session routing and nested worktrees

fix: harden session routing and nested worktrees #3188

Workflow file for this run

name: CI
on:
push:
branches: [master, feature/holographic-memory]
pull_request:
branches: ['**']
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: "0"
CARGO_PROFILE_DEV_DEBUG: "0"
CARGO_PROFILE_TEST_DEBUG: "0"
AST_GREP_VERSION: "0.44.0"
jobs:
commit-messages:
name: Commit Messages
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Reject tracked ignored files
run: scripts/check-release-pr-integrity.sh HEAD HEAD
- name: Validate PR commit messages
if: github.event_name == 'pull_request'
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: scripts/check-conventional-commits.sh "$BASE_SHA..$HEAD_SHA"
- name: Validate pushed commit messages
if: github.event_name == 'push'
env:
BEFORE_SHA: ${{ github.event.before }}
HEAD_SHA: ${{ github.sha }}
run: |
set -euo pipefail
if [ "$BEFORE_SHA" = "0000000000000000000000000000000000000000" ]; then
if git rev-parse "${HEAD_SHA}^" >/dev/null 2>&1; then
RANGE="${HEAD_SHA}^..${HEAD_SHA}"
else
RANGE="$HEAD_SHA"
fi
else
RANGE="${BEFORE_SHA}..${HEAD_SHA}"
fi
scripts/check-conventional-commits.sh "$RANGE"
release-version-drift:
name: Release Version Drift
if: github.event_name == 'pull_request' && !startsWith(github.head_ref, 'release-plz-')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v6
with:
python-version: "3.12"
- name: Test release version drift guard
run: bash tests/release_drift_check_test.sh
- name: Test release workflow contract
run: bash tests/release_workflow_contract_test.sh
- name: Test local dogfood command contract
run: bash tests/dogfood_command_test.sh
- name: Check release version drift
run: scripts/check-release-drift.sh
test:
name: Test ${{ matrix.name }}
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository || contains(fromJSON('["master","feature/holographic-memory"]'), github.event.pull_request.base.ref) }}
runs-on: ${{ fromJSON(matrix.runner) }}
strategy:
fail-fast: false
matrix:
include:
- name: Linux
runner: '"ubuntu-latest"'
- name: macOS
runner: '"macos-14"'
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- uses: ./.github/actions/setup-linux-mold
- uses: actions/setup-node@v4
with:
node-version: 22
- uses: actions/setup-python@v6
with:
python-version: "3.12"
- name: Install ast-grep
run: |
npm install --global "@ast-grep/cli@${AST_GREP_VERSION}"
ast-grep --version
- name: Install cargo-nextest
uses: taiki-e/install-action@nextest
- name: Cache Rust build outputs
uses: Swatinem/rust-cache@v2
with:
shared-key: ci-test-full-${{ runner.os }}
cache-on-failure: true
- name: Run tests
run: cargo nextest run --workspace --profile ci --locked
windows-build:
name: Build Windows tests
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository || contains(fromJSON('["master","feature/holographic-memory"]'), github.event.pull_request.base.ref) }}
runs-on: windows-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v7
- name: Tune Windows runner for build I/O
shell: pwsh
run: |
# Defender real-time scanning taxes the compiler's many short-lived
# object/incremental files; hosted runners are elevated, so turn it
# off and exclude the hot paths as a fallback.
Set-MpPreference -DisableRealtimeMonitoring $true -ErrorAction SilentlyContinue
$exclusions = @(
$env:GITHUB_WORKSPACE,
$env:RUNNER_TEMP,
[System.IO.Path]::GetTempPath(),
$env:CARGO_HOME,
$env:RUSTUP_HOME,
"$env:USERPROFILE\.cargo",
"$env:USERPROFILE\.rustup"
) | Where-Object { $_ } | Select-Object -Unique
foreach ($path in $exclusions) {
Add-MpPreference -ExclusionPath $path -ErrorAction SilentlyContinue
}
- uses: dtolnay/rust-toolchain@stable
- name: Use lld-link linker
shell: pwsh
run: |
where.exe lld-link.exe
lld-link.exe --version
"CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER=lld-link.exe" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
# Node is only needed so build.rs can produce the embedded dashboard
# dist assets (npm ci + npm run build) once, instead of once per shard.
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
cache-dependency-path: dashboard/package-lock.json
- name: Install cargo-nextest
uses: taiki-e/install-action@nextest
- name: Cache Windows Rust build outputs
uses: Swatinem/rust-cache@v2
with:
shared-key: ci-test-full-windows-msvc-lld
cache-on-failure: true
- name: Build nextest archive
shell: pwsh
run: cargo nextest archive --workspace --locked --archive-file "$env:RUNNER_TEMP/nextest-archive.tar.zst"
- name: Upload nextest archive
uses: actions/upload-artifact@v4
with:
name: windows-nextest-archive
path: ${{ runner.temp }}/nextest-archive.tar.zst
retention-days: 1
compression-level: 0
windows-test-shard:
name: Test Windows shard ${{ matrix.partition }}/5
needs: windows-build
runs-on: windows-latest
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
partition: [1, 2, 3, 4, 5]
steps:
- uses: actions/checkout@v7
- name: Tune Windows runner for test I/O
shell: pwsh
run: |
# Defender real-time scanning taxes the many short-lived SQLite/tempdir
# files these tests create; hosted runners are elevated, so turn it off
# and exclude the hot paths as a fallback.
Set-MpPreference -DisableRealtimeMonitoring $true -ErrorAction SilentlyContinue
$exclusions = @(
$env:GITHUB_WORKSPACE,
$env:RUNNER_TEMP,
[System.IO.Path]::GetTempPath(),
$env:CARGO_HOME,
$env:RUSTUP_HOME,
"$env:USERPROFILE\.cargo",
"$env:USERPROFILE\.rustup"
) | Where-Object { $_ } | Select-Object -Unique
foreach ($path in $exclusions) {
Add-MpPreference -ExclusionPath $path -ErrorAction SilentlyContinue
}
# Default user TEMP is on the slow C: drive; RUNNER_TEMP shares the
# faster workspace drive. Point TMP/TEMP there so tempfile::TempDir in
# the nextest test processes follows.
$fastTemp = Join-Path $env:RUNNER_TEMP "tmp"
New-Item -ItemType Directory -Path $fastTemp -Force | Out-Null
"TMP=$fastTemp" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
"TEMP=$fastTemp" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
- uses: actions/setup-node@v4
with:
node-version: 22
- uses: actions/setup-python@v6
with:
python-version: "3.12"
- name: Install ast-grep
shell: pwsh
run: |
npm install --global "@ast-grep/cli@$env:AST_GREP_VERSION"
ast-grep --version
- name: Install cargo-nextest
uses: taiki-e/install-action@nextest
- name: Download nextest archive
uses: actions/download-artifact@v4
with:
name: windows-nextest-archive
path: ${{ runner.temp }}
- name: Run Windows tests
env:
TRACEDECAY_SQLITE_UNSAFE_FAST: "1"
shell: pwsh
# --extract-to must be the workspace so target/ lands at the same
# absolute path as on the build job: integration tests bake
# env!("CARGO_BIN_EXE_tracedecay") into the binaries at compile time.
run: >-
cargo-nextest nextest run --profile ci
--archive-file "$env:RUNNER_TEMP/nextest-archive.tar.zst"
--extract-to "$env:GITHUB_WORKSPACE"
--workspace-remap "$env:GITHUB_WORKSPACE"
--partition slice:${{ matrix.partition }}/5
--test-threads num-cpus --status-level slow
- name: Clean abandoned Windows test children
if: always()
shell: pwsh
run: |
$workspace = (Resolve-Path $env:GITHUB_WORKSPACE).Path
$all = Get-CimInstance Win32_Process
$liveProcessIds = [System.Collections.Generic.HashSet[uint32]]::new()
foreach ($process in $all) {
[void]$liveProcessIds.Add([uint32]$process.ProcessId)
}
$stale = foreach ($process in $all) {
if ($process.Name -ne "tracedecay.exe") {
continue
}
if (-not $process.CommandLine) {
continue
}
$inCurrentWorkspace = $process.CommandLine.IndexOf($workspace, [StringComparison]::OrdinalIgnoreCase) -ge 0
if (-not $inCurrentWorkspace) {
continue
}
if (-not $liveProcessIds.Contains([uint32]$process.ParentProcessId)) {
$process
}
}
foreach ($process in $stale) {
Write-Host "Stopping abandoned tracedecay child pid=$($process.ProcessId)"
Stop-Process -Id $process.ProcessId -Force -ErrorAction SilentlyContinue
}
- name: Upload Windows nextest report
if: always()
uses: actions/upload-artifact@v4
with:
name: windows-nextest-junit-${{ matrix.partition }}
path: target/nextest/ci/junit.xml
if-no-files-found: ignore
retention-days: 7
windows-test:
name: Test Windows
if: ${{ !cancelled() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository || contains(fromJSON('["master","feature/holographic-memory"]'), github.event.pull_request.base.ref)) }}
needs: windows-test-shard
runs-on: ubuntu-latest
steps:
- name: Check Windows shards
run: |
if [ "${{ needs.windows-test-shard.result }}" != "success" ]; then
echo "Windows shard result: ${{ needs.windows-test-shard.result }}"
exit 1
fi
echo "All Windows shards passed."
clippy:
name: Clippy
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository || contains(fromJSON('["master","feature/holographic-memory"]'), github.event.pull_request.base.ref) }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- uses: ./.github/actions/setup-linux-mold
- name: Cache Rust build outputs
uses: Swatinem/rust-cache@v2
with:
shared-key: ci-clippy-full-${{ runner.os }}
cache-on-failure: true
- name: Run blocking Clippy policy
run: cargo clippy --workspace --all-targets --locked -- -D warnings
fmt:
name: Format
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository || contains(fromJSON('["master","feature/holographic-memory"]'), github.event.pull_request.base.ref) }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- run: cargo fmt --all -- --check
dashboard:
name: Dashboard
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository || contains(fromJSON('["master","feature/holographic-memory"]'), github.event.pull_request.base.ref) }}
runs-on: ubuntu-latest
timeout-minutes: 25
env:
DASHBOARD_DIST_FILES: >-
shell/dist/shell.js
shell/dist/shell.css
holographic/dist/index.js
holographic/dist/style.css
lcm/dist/index.js
lcm/dist/style.css
graph/dist/index.js
graph/dist/style.css
savings/dist/index.js
savings/dist/style.css
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v6
with:
node-version: "22"
cache: npm
cache-dependency-path: dashboard/package-lock.json
- name: Install dashboard dependencies
working-directory: dashboard
run: npm ci
- name: Build dashboard assets
working-directory: dashboard
run: npm run build
- name: Run dashboard unit tests
working-directory: dashboard
run: npm test
- name: Verify embedded dist artifacts
working-directory: dashboard
run: |
set -euo pipefail
for f in $DASHBOARD_DIST_FILES; do
if [ ! -s "$f" ]; then
echo "missing or empty dist artifact: $f" >&2
exit 1
fi
done
# shellcheck disable=SC2086
sha256sum $DASHBOARD_DIST_FILES > /tmp/dashboard-dist.sha
npm run build
sha256sum -c /tmp/dashboard-dist.sha
- uses: dtolnay/rust-toolchain@stable
- uses: ./.github/actions/setup-linux-mold
- name: Install ast-grep
run: |
npm install --global "@ast-grep/cli@${AST_GREP_VERSION}"
ast-grep --version
- name: Install cargo-nextest
uses: taiki-e/install-action@nextest
- name: Cache Rust build outputs
uses: Swatinem/rust-cache@v2
with:
shared-key: ci-dashboard-full-${{ runner.os }}
cache-on-failure: true
- name: Run dashboard integration tests
run: cargo nextest run --test dashboard_api_test --no-fail-fast --locked
- name: Cache Playwright browsers
uses: actions/cache@v6
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ hashFiles('dashboard/package-lock.json') }}
restore-keys: |
playwright-${{ runner.os }}-
- name: Install Playwright Chromium
working-directory: dashboard
run: npx playwright install --with-deps chromium
- name: Prebuild dashboard binary for smoke
run: cargo build --bin tracedecay --locked
- name: Run dashboard smoke (empty LCM)
working-directory: dashboard
env:
TRACEDECAY_GLOBAL_DB: ${{ runner.temp }}/tracedecay-dashboard-smoke-empty.db
run: |
set -euo pipefail
rm -f "$TRACEDECAY_GLOBAL_DB"
attempts=0
max_attempts=3
until [ "$attempts" -ge "$max_attempts" ]; do
attempts=$((attempts + 1))
echo "Smoke attempt ${attempts}/${max_attempts}..."
if npm run smoke -- --expect-lcm=empty; then
exit 0
fi
if [ "$attempts" -lt "$max_attempts" ]; then
sleep 10
fi
done
echo "Dashboard smoke failed after ${max_attempts} attempts" >&2
exit 1
- name: Run dashboard mobile smoke
working-directory: dashboard
env:
TRACEDECAY_GLOBAL_DB: ${{ runner.temp }}/tracedecay-dashboard-smoke-mobile.db
run: |
set -euo pipefail
rm -f "$TRACEDECAY_GLOBAL_DB"
npm run smoke:mobile -- --expect-lcm=empty
hermes-integration:
name: Hermes integration (stock)
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository || contains(fromJSON('["master","feature/holographic-memory"]'), github.event.pull_request.base.ref) }}
runs-on: ubuntu-latest
timeout-minutes: 30
env:
# Stock (upstream) Hermes pin — the generated plugin must keep working
# against this exact upstream commit. Bump deliberately after rerunning
# scripts/hermes_stock_integration.sh against the new ref locally.
HERMES_UPSTREAM_REPO: https://github.com/NousResearch/hermes-agent.git
HERMES_UPSTREAM_REF: 9dd9ef0ec99a87f078f7272b4323df5440b4b3f9
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- uses: ./.github/actions/setup-linux-mold
- name: Install ast-grep
run: |
npm install --global "@ast-grep/cli@${AST_GREP_VERSION}"
ast-grep --version
- name: Cache Rust build outputs
uses: Swatinem/rust-cache@v2
with:
shared-key: ci-hermes-integration-full-${{ runner.os }}
cache-on-failure: true
- name: Build tracedecay binary
run: cargo build --bin tracedecay --locked
- name: Run generated-plugin unit checks (no Hermes required)
run: python3 scripts/hermes_plugin_unit_check.py
- name: Clone stock Hermes at pinned ref
run: |
set -euo pipefail
git init -q "$RUNNER_TEMP/hermes-upstream"
git -C "$RUNNER_TEMP/hermes-upstream" remote add origin "$HERMES_UPSTREAM_REPO"
git -C "$RUNNER_TEMP/hermes-upstream" fetch --depth 1 origin "$HERMES_UPSTREAM_REF"
git -C "$RUNNER_TEMP/hermes-upstream" checkout --detach FETCH_HEAD
- uses: astral-sh/setup-uv@v8.2.0
with:
enable-cache: true
cache-dependency-glob: ${{ runner.temp }}/hermes-upstream/uv.lock
cache-suffix: hermes-${{ env.HERMES_UPSTREAM_REF }}
- name: Set up stock Hermes environment
working-directory: ${{ runner.temp }}/hermes-upstream
run: uv sync --frozen --no-dev
- name: Run stock Hermes integration checks
env:
TRACEDECAY_BIN: ${{ github.workspace }}/target/debug/tracedecay
HERMES_UPSTREAM_DIR: ${{ runner.temp }}/hermes-upstream
run: scripts/hermes_stock_integration.sh