diff --git a/.github/docker/Dockerfile.ci b/.github/docker/Dockerfile.ci index ebf4a4d13..99591ebd2 100644 --- a/.github/docker/Dockerfile.ci +++ b/.github/docker/Dockerfile.ci @@ -28,9 +28,13 @@ RUN printf 'Acquire::Retries "5";\nAcquire::http::Timeout "30";\nAcquire::https: # System deps (retry apt-get update + install as a unit — even Hetzner can blip). # Includes xz-utils so the Node.js .tar.xz download below can decompress. +# python3: bin/gstack-jsonl-merge, gstack-brain-sync, gstack-detach, and other +# bash bins shell out to it (macOS ships python3; the base image doesn't). +# file: skill-validation's no-compiled-binaries-in-git check runs `file --mime-type`. +# poppler-utils: make-pdf's e2e gates hard-require pdftotext/pdffonts/pdfinfo in CI. RUN for i in 1 2 3; do \ apt-get update && apt-get install -y --no-install-recommends \ - git curl unzip xz-utils ca-certificates jq bc gpg && break || \ + git curl unzip xz-utils ca-certificates jq bc gpg python3 file poppler-utils && break || \ (echo "apt retry $i/3 after failure"; sleep 10); \ done \ && rm -rf /var/lib/apt/lists/* @@ -61,10 +65,14 @@ RUN curl --retry 5 --retry-delay 5 --retry-connrefused -fsSL "https://nodejs.org && node --version \ && npm --version -# Bun (install to /usr/local so non-root users can access it) +# Bun (install to /usr/local so non-root users can access it). +# The version MUST be passed as a positional arg — bun.sh/install ignores a +# BUN_VERSION env var, so the old `| BUN_VERSION=x.y.z bash` form silently +# installed latest on every image rebuild (observed: 1.3.13/1.3.14 drift vs +# the 1.3.10 devs run locally). ENV BUN_INSTALL="/usr/local" RUN curl --retry 5 --retry-delay 5 --retry-connrefused -fsSL https://bun.sh/install \ - | BUN_VERSION=1.3.10 bash + | bash -s "bun-v1.3.10" # Claude CLI RUN npm i -g @anthropic-ai/claude-code @@ -82,8 +90,10 @@ RUN npx playwright install-deps chromium # (headed-xvfb, headed-orphan-cleanup) can exercise the Linux container # auto-spawn path on every CI run. Without Xvfb in the image, the most # common production --headed path goes untested. +# fonts-noto-color-emoji: the make-pdf emoji render gate needs a color-emoji +# fallback font (mirrors make-pdf-gate.yml's Ubuntu setup step). RUN for i in 1 2 3; do \ - apt-get update && apt-get install -y --no-install-recommends fonts-liberation fontconfig xvfb x11-utils && break || \ + apt-get update && apt-get install -y --no-install-recommends fonts-liberation fonts-noto-color-emoji fontconfig xvfb x11-utils && break || \ (echo "fonts-liberation install retry $i/3"; sleep 10); \ done \ && fc-cache -f \ @@ -105,6 +115,7 @@ RUN npx playwright install chromium \ # Verify everything works RUN bun --version && node --version && claude --version && jq --version && gh --version \ + && python3 --version && command -v file && command -v pdftotext && command -v pdffonts && command -v pdfinfo \ && npx playwright --version \ && fc-match "Liberation Sans" | grep -qi "Liberation" \ || (echo "ERROR: fonts-liberation not installed — make-pdf PDFs will render in DejaVu Sans" && exit 1) diff --git a/.github/workflows/free-tests.yml b/.github/workflows/free-tests.yml index 7feae697d..05a2afde5 100644 --- a/.github/workflows/free-tests.yml +++ b/.github/workflows/free-tests.yml @@ -83,6 +83,17 @@ jobs: 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 @@ -132,6 +143,7 @@ jobs: [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 ' ') @@ -153,6 +165,17 @@ jobs: 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"