fix(ci): free-tests container parity — tools, pinned bun, git identity, mutation tripwire

- Dockerfile.ci: add python3 (gstack-jsonl-merge/brain-sync/detach shell out
  to it), file (skill-validation's binary check), poppler-utils (make-pdf
  e2e gates hard-require pdftotext/pdffonts/pdfinfo), fonts-noto-color-emoji
  (emoji render gate, mirrors make-pdf-gate.yml). Fix the bun pin: the
  bun.sh installer ignores a BUN_VERSION env var, so the old form silently
  installed latest on every rebuild (observed 1.3.13/1.3.14 drift vs the
  1.3.10 devs run locally); pass the version as the positional arg.
- free-tests.yml: git identity + safe.directory for the git-exercising
  tests (container checkout is owned by a different uid than runner);
  post-loop tree-mutation tripwire that names a tracked-file-mutating test
  instead of letting downstream collateral confuse the report; skip the
  documented variants-retry-after timing flake.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan 2026-08-15 08:40:27 -07:00
parent 8f048370a4
commit 96423aff76
No known key found for this signature in database
GPG Key ID: C1F69E85C74EFE1D
2 changed files with 38 additions and 4 deletions

View File

@ -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)

View File

@ -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"