From f48b890f2f9094c4f8af132965e5be5313d0a04a Mon Sep 17 00:00:00 2001 From: Vyncint Ng <115854244+vyncint@users.noreply.github.com> Date: Tue, 27 Oct 2026 15:15:31 +0700 Subject: [PATCH 1/5] fix(report): survive an unreadable directory and set INSTA_UPDATE in CI The render step ran `find .` under `set -euo pipefail`. find exits 1 on any directory it cannot read, so a root-owned cache or a chmod 000 test fixture in the workspace ended the step before the summary had a line and before anything was uploaded. Both finds now keep their output and drop their status. insta's default, INSTA_UPDATE=auto, writes no .snap.new once CI is set (insta 1.49.0 src/env.rs), so the setup the README, the action's README and the skill showed never left a snapshot for the action on GitHub Actions. Every snippet, and this repository's own test step, now sets INSTA_UPDATE: new. Inline snapshots go to .pending-snap files, JSON records the CLI does not read; they are not collected, and the action's README and its "nothing found" summary say so. tools/report-action-selftest/run.sh runs the step out of action.yml with a stub CLI: an unreadable directory beside a .snap.new and inside the artifact directory, and a run with nothing to show. Removing either `|| true`, the INSTA_UPDATE sentence or the target/ prune turns it red. Closes #597 Signed-off-by: Vyncint Ng <115854244+vyncint@users.noreply.github.com> --- .github/actions/report/README.md | 9 ++ .github/actions/report/action.yml | 17 +++- .github/workflows/ci.yml | 7 ++ CHANGELOG.md | 9 ++ CONTRIBUTING.md | 1 + README.md | 9 +- skills/termlens/SKILL.md | 12 ++- tools/report-action-selftest/run.sh | 122 ++++++++++++++++++++++++++++ 8 files changed, 176 insertions(+), 10 deletions(-) create mode 100755 tools/report-action-selftest/run.sh diff --git a/.github/actions/report/README.md b/.github/actions/report/README.md index 727d5fb..83f6eb6 100644 --- a/.github/actions/report/README.md +++ b/.github/actions/report/README.md @@ -10,10 +10,19 @@ as SVG/HTML in an uploaded artifact. - run: cargo test env: TERMLENS_ARTIFACT_DIR: ${{ runner.temp }}/termlens + INSTA_UPDATE: new - uses: vyncint/termlens/.github/actions/report@v0.11.6 if: failure() ``` +`INSTA_UPDATE: new` is what makes insta write a `.snap.new` on CI. Its +default, `auto`, writes none when the `CI` variable is set, and GitHub sets +it on every runner, so without the line a failing snapshot leaves nothing +here to show. Inline snapshots (`assert_snapshot!(value, @"…")`) are not +rendered: insta keeps their new values in `.pending-snap` files, JSON +records for `cargo insta review` rather than a screen, and the action does +not collect them. + Nothing to install: the action `cargo install`s `termlens-cli` on the runner. Inputs: `artifact-dir` (defaults to `TERMLENS_ARTIFACT_DIR`, else `$RUNNER_TEMP/termlens`), `cli-version` (a `termlens-cli` version; empty diff --git a/.github/actions/report/action.yml b/.github/actions/report/action.yml index b0f6e73..4f4436b 100644 --- a/.github/actions/report/action.yml +++ b/.github/actions/report/action.yml @@ -6,7 +6,13 @@ # SVG/HTML renderings as an artifact, since a step summary shows text and # not a local image. # -# One `uses:` line from a consumer's workflow, nothing else: the CLI is +# insta writes a `.snap.new` on CI only when the test step sets +# `INSTA_UPDATE: new`: its default, `auto`, writes nothing once `CI` is set, +# and every GitHub runner sets it (#597). Inline snapshots are not collected: +# insta keeps their new values in `.pending-snap` files, JSON records for +# `cargo insta review` rather than a screen `termlens render` reads. +# +# One `uses:` line from a consumer's workflow, nothing to install: the CLI is # `cargo install`ed from crates.io, which every Rust runner already has. # This repository passes `cli: workspace` so the suite dogfoods the CLI in # the tree rather than the one last published. @@ -74,9 +80,13 @@ runs: # What to show: every .snap.new outside target/, then every file the # artifact hook wrote. Sorted, so the summary is stable run to run. + # `find` exits 1 on any directory it cannot read, a root-owned cache + # or a chmod 000 fixture, after printing everything it could; under + # pipefail that status ended the step before the summary had a line + # in it (#597). The output is kept and the status is not. { - find . -path ./target -prune -o -name '*.snap.new' -print 2>/dev/null - if [ -d "$dir" ]; then find "$dir" -type f -print; fi + find . -path ./target -prune -o -name '*.snap.new' -print 2>/dev/null || true + if [ -d "$dir" ]; then find "$dir" -type f -print || true; fi } | sort > "${out}/files.txt" { @@ -84,6 +94,7 @@ runs: echo if [ ! -s "${out}/files.txt" ]; then echo "No \`.snap.new\` files and nothing under \`${dir}\`: the failures left no screen behind." + echo "On CI, insta writes a \`.snap.new\` only when the test step sets \`INSTA_UPDATE: new\`; inline snapshots (\`.pending-snap\`) are not rendered here." fi } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 85dd2a8..7501a27 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,6 +67,9 @@ jobs: - run: cargo test --workspace --all-features env: TERMLENS_ARTIFACT_DIR: ${{ runner.temp }}/termlens + # insta's default, `auto`, writes no .snap.new once CI is set, so + # the report step below never had a snapshot to show (#597). + INSTA_UPDATE: new # The CLI's documented exit codes, against this tree. `install.yml` # runs the same script against the *published* binary, which is the # net; this is the fast feedback. #310 was a `--version` that exited 2 @@ -208,6 +211,10 @@ jobs: # all-passing fixture must exit zero. Needs no toolchain, so it lives # here with the other script gates. - run: tools/preflight-selftest/run.sh + # …and the report action's render step (#597), run from action.yml + # with a stub CLI: a directory `find` cannot read ended the step + # before it wrote a line, and other repositories run this step. + - run: tools/report-action-selftest/run.sh # The emit fixture's documented steps against the ones it implements # (#318). It lives in this job because it needs no toolchain — it is # two greps and a `comm` — and the fixture is what almost every diff --git a/CHANGELOG.md b/CHANGELOG.md index 944b7bb..723a331 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -101,6 +101,15 @@ reads that marker. also write a character XML 1.0 forbids, a C0 control other than tab, LF and CR or U+FFFE/U+FFFF, as U+FFFD, so a window title holding `\x01` or a cell holding U+FFFF no longer makes an SVG no parser opens. +- The `report` action writes its summary when the workspace holds a + directory `find` cannot read, such as a root-owned cache or a `chmod 000` + test fixture (#597). The step used to stop there with exit 1 before + writing anything. The CI snippets in the README, the action's README and + the skill now set `INSTA_UPDATE: new` beside `TERMLENS_ARTIFACT_DIR`: + insta's default writes no `.snap.new` when `CI` is set, so on GitHub + Actions a failing snapshot left the action nothing to show. Inline + snapshots are not rendered, and the action's README and its "nothing + found" summary now say so. ## [0.11.6] - 2026-10-14 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bdf0f25..04447da 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -74,6 +74,7 @@ tools/report-pin-gate-selftest/run.sh # …and the report-pin tools/attribution-gate-selftest/run.sh # …and the AI-attribution gate, likewise tools/dco-gate-selftest/run.sh # …and the DCO gate, its one-commit squash exemption included tools/preflight-selftest/run.sh # …and the runner accounts for each gate it runs, failures included +tools/report-action-selftest/run.sh # …and the report action writes its summary past a directory it cannot read .github/scripts/check-semver.sh 0.11.6 # …and the public API is compatible with the last published release ``` diff --git a/README.md b/README.md index 4964ab5..93a0a7e 100644 --- a/README.md +++ b/README.md @@ -223,14 +223,17 @@ termlens render --svg --out shot.svg - # …to a file, from a screen on stdin ``` In CI, set `TERMLENS_ARTIFACT_DIR` on the test step and every screen a -failing wait embeds is also written there. The report action then puts -those screens, and every `.snap.new` with its diff, into the pull request's -step summary: +failing wait embeds is also written there. Set `INSTA_UPDATE: new` beside +it: insta's default writes no `.snap.new` when `CI` is set, and GitHub sets +`CI` on every runner. The report action then puts those screens, and every +`.snap.new` with its diff, into the pull request's step summary (inline +snapshots are not rendered): ```yaml - run: cargo test env: TERMLENS_ARTIFACT_DIR: ${{ runner.temp }}/termlens + INSTA_UPDATE: new - uses: vyncint/termlens/.github/actions/report@v0.11.6 if: failure() ``` diff --git a/skills/termlens/SKILL.md b/skills/termlens/SKILL.md index e0c069d..7713e1f 100644 --- a/skills/termlens/SKILL.md +++ b/skills/termlens/SKILL.md @@ -567,10 +567,14 @@ directory (see §9b). `diff` and `render` read `-` as stdin (one operand of `diff` at most), and `inspect --cwd DIR` runs the program somewhere other than here; a relative program path such as `./demo` is then found inside `DIR`. -- In CI, set `TERMLENS_ARTIFACT_DIR: ${{ runner.temp }}/termlens` on the - test step and add `uses: vyncint/termlens/.github/actions/report@v0.11.6` - with `if: failure()` after it: every screen a failing wait embedded, and - every `.snap.new` with its diff, lands in the pull request's step summary. +- In CI, set `TERMLENS_ARTIFACT_DIR: ${{ runner.temp }}/termlens` and + `INSTA_UPDATE: new` on the test step, and add + `uses: vyncint/termlens/.github/actions/report@v0.11.6` with + `if: failure()` after it: every screen a failing wait embedded, and every + `.snap.new` with its diff, lands in the pull request's step summary. + `INSTA_UPDATE: new` is not optional: insta's default writes no `.snap.new` + when `CI` is set, and GitHub sets it on every runner. Inline snapshots are + not rendered; insta keeps their new values in `.pending-snap` files. ## 10. A checklist before you finish diff --git a/tools/report-action-selftest/run.sh b/tools/report-action-selftest/run.sh new file mode 100755 index 0000000..67226c9 --- /dev/null +++ b/tools/report-action-selftest/run.sh @@ -0,0 +1,122 @@ +#!/usr/bin/env bash +# Proves the report action's render step survives the workspaces it meets, +# before a green run of it is trusted (#597). +# +# The step's `run:` block is read out of .github/actions/report/action.yml +# and run as the runner runs it, under bash with `set -euo pipefail`, in a +# scratch workspace. termlens-cli is a stub that echoes its arguments: what +# is under test is the shell around the CLI, not the CLI. +# +# - a directory nobody can read beside a .snap.new: `find` exits 1 on it, +# and that status ended the step before the summary had a line (#597); +# the .snap.new must still reach the summary +# - the same inside the artifact directory, which the step searches with +# a second `find` +# - nothing to show, with a .snap.new under target/ that must stay out: +# the summary says so and names INSTA_UPDATE +# +# Root reads a chmod 000 directory anyway, so as root the first two cases +# cannot fail and are skipped by name rather than passed. +# +# Usage: tools/report-action-selftest/run.sh +set -euo pipefail + +cd "$(dirname "${BASH_SOURCE[0]}")" +root="$(cd ../.. && pwd)" +action="$root/.github/actions/report/action.yml" +status=0 + +body="$(awk ' + /^ - name: Render the screens into the summary$/ { step = 1; next } + step && /^ run: [|]$/ { run = 1; next } + run && /^$/ { print ""; next } + run && /^ / { print substr($0, 9); next } + run { exit } +' "$action")" +if [ -z "$body" ]; then + echo "::error::no \"Render the screens into the summary\" step with a run: block in $action" >&2 + exit 1 +fi + +scratch="$(mktemp -d)" +locked="" +trap 'for d in $locked; do chmod 755 "$d"; done; rm -rf "$scratch"' EXIT + +# `workspace `: a fresh workspace, artifact directory and RUNNER_TEMP +# for one case, with the stub CLI where the install step puts the real one. +workspace() { + ws="$scratch/$1/workspace" + artifacts="$scratch/$1/artifacts" + rt="$scratch/$1/runner-temp" + mkdir -p "$ws" "$artifacts" "$rt/termlens-cli/bin" + printf '#!/bin/sh\necho "stub termlens $*"\n' > "$rt/termlens-cli/bin/termlens" + chmod +x "$rt/termlens-cli/bin/termlens" +} + +unreadable() { + mkdir -p "$1" + chmod 000 "$1" + locked="$locked $1" +} + +# `expect