sed -r 's/\x1B[[0-9;][mK]//g' vitest.log > vitest.clean.log
Root cause. vitest emits ANSI SGR sequences even when stdout is a pipe (not a TTY), so tee'd output contains ESC[1mESC[32m47 passedESC[39m. The greedy \d+-anchored grep -oP 'Test Files\s+\d+ passed' cannot match across the interleaved escape codes, so SUITES/TESTS come back empty and the assert step fails with an empty-variable error.
Fix 1 — strip ANSI before parsing. Pipe the tee'd log through sed -r 's/\x1B\[[0-9;]*[mK]//g' — this removes CSI sequences (SGR color/bold codes ESC[...m and erase-line ESC[...K) but leaves all printable text untouched — then run the existing grep assertions on the sanitized stream:
- name: Run tests (tee'd for parsing)
run: |
npx vitest run --reporter=default 2>&1 | tee vitest.log
# Strip ANSI color codes that vitest emits even when piped (non-TTY):
sed -r 's/\x1B\[[0-9;]*[mK]//g' vitest.log > vitest.clean.log
- name: Assert parsed test counts
run: |
SUITES=$(grep -oP 'Test Files\s+\d+ passed' vitest.clean.log | grep -oP '\d+')
TESTS=$(grep -oP 'Tests\s+\d+ passed' vitest.clean.log | grep -oP '\d+')
echo "SUITES=$SUITES TESTS=$TESTS"
[ -n "$SUITES" ] || { echo "!! empty SUITES"; exit 1; }
[ -n "$TESTS" ] || { echo "!! empty TESTS"; exit 1; }
The regex \x1B\[[0-9;]*[mK] covers: plain SGR (ESC[32m), multi-parameter SGR (ESC[1;32m), extended 256-color (ESC[38;5;Nm) and truecolor (ESC[38;2;R;G;Bm), plus erase-line (ESC[2K, ESC[K) suffixes vitest appends when rendering. Because \x1B is the literal byte and [/] are character classes, the -r (ERE) mode needs no over-escaping and won't touch the word passed or the numbers.
Fix 2 — branch trigger. The [main, develop] push filter silently skipped 16 pushes on the active feat/native-s3 branch, so the CI gate never ran for that work. Add the feature branch (or a glob) to the filter; or, if every push should gate, drop the filter entirely:
on:
push:
branches: [main, develop, "feat/*"] # now covers feat/native-s3
pull_request:
(Alternative if the pipeline is PR-gated anyway: keep push restricted and rely on pull_request triggers, or use branches-ignore.)
I reproduced the failure and verified the fix in `/tmp/vitest-ansi-verify` with a generator that mimics vitest's piped output, then tested 7 edge cases. Actual results: | Test | Outcome | |---|---| | Raw tee'd log starts `1b 5b 31 6d 1b 5b 33 32 6d...` (confirmed via `xxd`) | ANSI present as expected | | **Buggy:** `grep -oP 'Test Files\s+\d+ passed' vitest.log` | **MATCHED NOTHING → empty SUITES/TESTS → step fails** | | **Fixed:** `sed -r 's/\x1B\[[0-9;]*[mK]//g' vitest.log \| grep -oP ...` | `Test Files 47 passed`, `Tests 132 passed`, `Assertions 2 failed` | | Edge 1: already ANSI-free input | no-op, still matches (`Test Files 3 passed`) | | Edge 2: zero counts (`0 passed`) | matches — `\d+` accepts 0 | | Edge 3: `ESC[2K`/`ESC[K` clear-line codes | stripped, `Tests 7 passed` matches | | Edge 4: truecolor `ESC[38;2;255;0;0m` | stripped, `Test Files 11 passed` matches | | Edge 5: `failed`/`skipped` lines | survive stripping and match | | Edge 6: full step semantics (`SUITES=$(grep ... \| grep -oP '\d+')`) | `SUITES=47 TESTS=132`, step PASSES with `-n` guards | | Edge 7: branch filter | `branches: [main, develop, "feat/*"]` covers `feat/native-s3`; `branches-ignore` alternative documented | No regression risk: the sed transform only removes bytes in the `ESC [ … m|K` shape, which cannot appear in normal test names or numeric output, so counts and assertions are unaffected for clean logs. ---
{"model": "deepseek-v4-flash", "problem_class": "ci-github-actions-vitest-ansi-parse", "result": "passed", "tests": 7}