mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-09 22:48:57 +02:00
Merge origin/main (v1.64.1.0) into garrytan/time-attack-fork-review
The code-smell wave refactored the tunnel start into the staged startTunnel helper; the merge adopts that structure and threads this branch's truthful consent strings through its consent parameter (the receipt now names the isPairAgentEnabled gate at both call sites). The eng-review PTY warmup takes both improvements: main's resolveClaudeBinary fallback and this branch's resolveEvalModel kind. Resolver imports union; carve budgets take the larger of both waves' measured values (parity suite green on the merged tree); conflicted generated SKILL.md files regenerated from resolved sources. VERSION stays 1.65.0.0 over main's 1.64.1.0; CHANGELOG stacks 1.65.0.0 > 1.64.1.0 > 1.64.0.0.
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
name: Free Tests
|
||||
# The full free suite (`bun test`: browse/test/ + test/ + make-pdf/test/ minus
|
||||
# paid evals) previously ran in NO CI job — only Windows curated shards, paid
|
||||
# evals, and doc-freshness gates existed. Two test files crashed at module load
|
||||
# for 48 versions without any signal. This job closes that hole.
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: free-tests-${{ github.head_ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
IMAGE: ghcr.io/${{ github.repository }}/ci
|
||||
|
||||
jobs:
|
||||
# Same cached pre-baked toolchain image as evals.yml (only rebuilds on
|
||||
# Dockerfile/lockfile change).
|
||||
build-image:
|
||||
runs-on: ubicloud-standard-8
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
outputs:
|
||||
image-tag: ${{ steps.meta.outputs.tag }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- id: meta
|
||||
run: echo "tag=${{ env.IMAGE }}:${{ hashFiles('.github/docker/Dockerfile.ci', 'package.json', 'bun.lock') }}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Check if image exists
|
||||
id: check
|
||||
run: |
|
||||
if docker manifest inspect ${{ steps.meta.outputs.tag }} > /dev/null 2>&1; then
|
||||
echo "exists=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "exists=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- if: steps.check.outputs.exists == 'false'
|
||||
run: cp package.json bun.lock .github/docker/
|
||||
|
||||
- if: steps.check.outputs.exists == 'false'
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .github/docker
|
||||
file: .github/docker/Dockerfile.ci
|
||||
push: true
|
||||
tags: |
|
||||
${{ steps.meta.outputs.tag }}
|
||||
${{ env.IMAGE }}:latest
|
||||
|
||||
free-tests:
|
||||
runs-on: ubicloud-standard-8
|
||||
needs: build-image
|
||||
container:
|
||||
image: ${{ needs.build-image.outputs.image-tag }}
|
||||
credentials:
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
options: --user runner
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# Bun creates root-owned temp dirs during Docker build. GH Actions runs as
|
||||
# runner user with HOME=/github/home. Redirect bun's cache to a writable dir.
|
||||
- name: Fix bun temp
|
||||
run: |
|
||||
mkdir -p /home/runner/.cache/bun
|
||||
{
|
||||
echo "BUN_INSTALL_CACHE_DIR=/home/runner/.cache/bun"
|
||||
echo "BUN_TMPDIR=/home/runner/.cache/bun"
|
||||
echo "TMPDIR=/home/runner/.cache"
|
||||
} >> "$GITHUB_ENV"
|
||||
|
||||
# Several test files exercise real git operations (gstack-artifacts-init,
|
||||
# session-update-autostash, team-mode, brain-sync) and bins that read the
|
||||
# current branch (gstack-decision-search). The container checkout is owned
|
||||
# by a different uid than `runner`, so git needs safe.directory, and
|
||||
# commit-making tests need an identity.
|
||||
- name: Git identity for git-exercising tests
|
||||
run: |
|
||||
git config --global user.email "ci@gstack.invalid"
|
||||
git config --global user.name "gstack CI"
|
||||
git config --global --add safe.directory '*'
|
||||
|
||||
# Same restore rationale as evals.yml: recursive copy beats symlink
|
||||
# (realpath escapes workspace) and hardlink (cross-device overlay-fs).
|
||||
- name: Restore deps
|
||||
run: |
|
||||
if [ -d /opt/node_modules_cache ] && diff -q /opt/node_modules_cache/.package.json package.json >/dev/null 2>&1; then
|
||||
cp -r /opt/node_modules_cache node_modules
|
||||
else
|
||||
bun install
|
||||
fi
|
||||
|
||||
- run: bun run build
|
||||
|
||||
# Fail fast if the container can't launch Chromium — the browse
|
||||
# integration tests need it.
|
||||
- name: Verify Chromium
|
||||
run: |
|
||||
echo "whoami=$(whoami) HOME=$HOME TMPDIR=${TMPDIR:-unset}"
|
||||
bun -e "import {chromium} from 'playwright';const b=await chromium.launch({args:['--no-sandbox']});console.log('Chromium OK');await b.close()"
|
||||
|
||||
# ONE BUN PROCESS PER FILE, on purpose. A single multi-file `bun test`
|
||||
# run of this suite is structurally unreliable here — observed twice
|
||||
# while building this job:
|
||||
# 1. Silent truncation: server-lifecycle tests stub process.exit, and
|
||||
# shutdown's async timers can hit the REAL exit after restore,
|
||||
# killing the whole bun process mid-suite with exit 0 and NO
|
||||
# summary (died at file 47, then file 51, of 358).
|
||||
# 2. Co-run state bleed: files green in isolation failed under
|
||||
# multi-file module sharing.
|
||||
# Per-file spawning makes truncation impossible by construction (the
|
||||
# census drives the loop; a killed child is a recorded failure, not a
|
||||
# vanished suite) and also covers the old exit-0-on-module-load-error
|
||||
# Bun behavior. Same isolation model as scripts/test-paid-shards.ts.
|
||||
- name: Run free suite (per-file isolation)
|
||||
shell: bash
|
||||
run: |
|
||||
set -o pipefail
|
||||
# Container-incompatible files, each with a reason (same curated-
|
||||
# exclusion pattern as the Windows shards in test-free-shards.ts).
|
||||
# Anything NOT on this list that fails still fails the job. Trimming
|
||||
# this list is tracked follow-up work.
|
||||
declare -A SKIP=(
|
||||
[browse/test/compare-board.test.ts]="pre-existing env failure (also fails on dev machines; needs a display-shaped env)"
|
||||
[browse/test/handoff.test.ts]="needs the headed Chrome-for-Testing build (headless-only container)"
|
||||
[browse/test/snapshot.test.ts]="pre-existing env failure (viewport/tab timing under container load)"
|
||||
[browse/test/extension-sender-auth.test.ts]="extension identity checks need a real chrome-extension origin"
|
||||
[browse/test/security-sidepanel-dom.test.ts]="sidepanel DOM harness needs the extension loaded headed"
|
||||
[browse/test/terminal-agent-integration.test.ts]="real PTY round-trip; container TTY semantics differ"
|
||||
[browse/test/xvfb.test.ts]="tests xvfb management; container has no X server to manage"
|
||||
[browse/test/security-audit-r2.test.ts]="one behavioral tmpdir-allowlist test breaks under this job's TMPDIR override (bun temp-dir workaround above)"
|
||||
[design/test/variants-retry-after.test.ts]="known timing flake, tracked in TODOS.md (HTTP-date Retry-After rounding)"
|
||||
)
|
||||
FILES=$(bun run scripts/test-free-shards.ts --list | grep -E '^ (browse/|test/|make-pdf/|design/)' | sed 's/^ //')
|
||||
TOTAL=$(echo "$FILES" | wc -l | tr -d ' ')
|
||||
echo "Enumerated $TOTAL free test files"
|
||||
FAILED=""
|
||||
N=0
|
||||
SKIPPED=0
|
||||
for f in $FILES; do
|
||||
N=$((N+1))
|
||||
if [ -n "${SKIP[$f]:-}" ]; then
|
||||
echo "SKIP [$N/$TOTAL] $f — ${SKIP[$f]}"
|
||||
SKIPPED=$((SKIPPED+1))
|
||||
continue
|
||||
fi
|
||||
if ! bun test "$f" > /tmp/one.log 2>&1; then
|
||||
echo "FAIL [$N/$TOTAL] $f"
|
||||
tail -30 /tmp/one.log
|
||||
FAILED="$FAILED $f"
|
||||
fi
|
||||
done
|
||||
echo "Skipped $SKIPPED container-incompatible files (reasons above)."
|
||||
# Tree-mutation tripwire: a test that rewrites tracked files poisons
|
||||
# every later file in the loop with confusing failures (observed:
|
||||
# gstack-config's skill_prefix auto-relink patched 52 SKILL.md names,
|
||||
# failing five unrelated suites downstream). Name the real culprit.
|
||||
MUTATED=$(git status --porcelain --untracked-files=no)
|
||||
if [ -n "$MUTATED" ]; then
|
||||
echo ""
|
||||
echo "A test mutated tracked files in the working tree — later failures may be collateral:"
|
||||
echo "$MUTATED"
|
||||
FAILED="$FAILED [tree-mutation]"
|
||||
fi
|
||||
if [ -n "$FAILED" ]; then
|
||||
echo ""
|
||||
echo "Failed files:$FAILED"
|
||||
exit 1
|
||||
fi
|
||||
echo "All $((TOTAL-SKIPPED)) runnable files green."
|
||||
@@ -15,27 +15,32 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- run: bun install
|
||||
- name: Check Claude host freshness
|
||||
run: bun run gen:skill-docs
|
||||
- name: Verify Claude skill docs are fresh
|
||||
# One generation pass for ALL 10 hosts. gen-skill-docs --host all
|
||||
# hard-fails on any per-host generation error (scripts/gen-skill-docs.ts
|
||||
# aggregates failures and exits non-zero), so every host is gated on
|
||||
# "generates cleanly." Known limitation, on purpose: the 9 gitignored
|
||||
# host outputs (.agents/, .factory/, .kiro/, ...) are NOT byte-freshness
|
||||
# checked — `git diff` on ignored untracked paths is always empty (the
|
||||
# previous per-host `git diff -- .agents/` gates could never fail for
|
||||
# exactly that reason). Byte-freshness is enforced only for tracked
|
||||
# output (the Claude SKILL.md files), which the two steps below cover.
|
||||
- name: Generate all host skill docs
|
||||
run: bun run gen:skill-docs --host all
|
||||
- name: Verify tracked skill docs are fresh
|
||||
run: |
|
||||
git diff --exit-code || {
|
||||
echo "Generated SKILL.md files are stale. Run: bun run gen:skill-docs"
|
||||
echo "Generated SKILL.md files are stale. Run: bun run gen:skill-docs --host all"
|
||||
exit 1
|
||||
}
|
||||
- name: Check Codex host freshness
|
||||
run: bun run gen:skill-docs --host codex
|
||||
- name: Verify Codex skill docs are fresh
|
||||
# git diff misses NEW untracked files (e.g. a freshly added skill whose
|
||||
# generated SKILL.md was never committed). Fail on any untracked stray
|
||||
# the generator produced outside the gitignored host dirs.
|
||||
- name: Verify no untracked generated files
|
||||
run: |
|
||||
git diff --exit-code -- .agents/ || {
|
||||
echo "Generated Codex SKILL.md files are stale. Run: bun run gen:skill-docs --host codex"
|
||||
STRAYS=$(git status --porcelain --untracked-files=all | grep '^??' || true)
|
||||
if [ -n "$STRAYS" ]; then
|
||||
echo "Generator produced untracked files that are neither committed nor gitignored:"
|
||||
echo "$STRAYS"
|
||||
echo "Commit them (bun run gen:skill-docs --host all) or gitignore them."
|
||||
exit 1
|
||||
}
|
||||
- name: Generate Factory skill docs
|
||||
run: bun run gen:skill-docs --host factory
|
||||
- name: Verify Factory skill docs are fresh
|
||||
run: |
|
||||
git diff --exit-code -- .factory/ || {
|
||||
echo "Generated Factory SKILL.md files are stale. Run: bun run gen:skill-docs --host factory"
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user