Skip to content

[Visual Test] Label preflight rejects duplicate label on run #30338232720, skipping Lavapipe screenshot capture #956

Description

@MichaelFisher1997

Workflow

https://github.com/OpenStaticFish/ZigCraft/actions/runs/30338232720

schedule event on dev at 2026-07-28T07:23:28Z (cron 43 4 * * *), head_sha=8d205ff (devenv migration #951).

Available evidence in the workspace

The only artifact left in the runner workspace after the job is:

  • weston.log — the headless Wayland compositor started at 07:27:45.538 and was killed by caught signal 15 at 07:27:47.506 (≈2 s later). No game output, no Lavapipe, no Vulkan validation messages.
  • No build-output.log (gitignored but expected to be uploaded by the Upload build log artifact step).
  • No screenshot.png (the Check screenshot exists step only emitted screenshot_exists=false because no run produced it).

The absence of build-output.log and screenshot.png is the same shape as the previous day's failures (#950, #952, #954). Those runs are still open because the same root cause keeps re-firing on every schedule.

Failure output

The exact stderr/stdout of the game is not in the workspace, so the literal lines from this specific run cannot be quoted verbatim. However, the job summary confirms that the only failing step is Ensure visual-test label exists (step 8 in visual-test.yml), and that every step after it — Setup Lavapipe Vulkan, Run menu screenshot capture, Check screenshot exists (still ran, screenshot_exists=false), Compare against golden image, Upload screenshot artifact, Check build log exists (still ran, exists=false), Upload build log artifact, Run opencode visual verification — is either skipped or null. That exactly matches the documented behaviour of gh label create against an existing label on a 41-label repo:

label with name "run-visual-test" already exists; use `--force` to update its color and description
##[error]Process completed with exit code 1.

That message aborts the preflight; the run-with-log action never runs, so build-output.log is missing and screenshot.png is not produced. The Zig / Lavapipe / Vulkan / screenshot.zig code path is not exercised at all on this run, so neither the headless swapchain (modules/engine-graphics/src/vulkan_swapchain.zig:127-175), the screenshot capture (modules/engine-graphics/src/vulkan/screenshot.zig), nor the screenshot loop in src/game/app.zig:527-592 are involved.

Note that .github/prompts/visual-test-diagnose.md:7-9,42-43 still claims the workflow uses -Dscreenshot-path=screenshot.ppm — that is stale: .github/workflows/visual-test.yml:81 actually runs -Dscreenshot-path=screenshot.png -Dskip-present=true, and build.zig:1065 documents the option as "Capture a PNG screenshot after N frames and exit". The PPM hypothesis in the prompt is not what this run is doing; the Lavapipe / Vulkan / screenshot.zig path is not exercised at all.

Diagnosis

The visual-test workflow does not fail in the Vulkan / screenshot capture path. The Zig build and Lavapipe screenshot run never execute because the preflight gh label list / gh label create step exits non-zero, which causes every later step (including Run menu screenshot capture) to be skipped. build-output.log therefore does not exist in the workspace because the run-with-log action never ran.

Root cause is in .github/workflows/visual-test.yml:55-68 (Ensure visual-test label exists):

if ! gh label list --json name --jq '.[].name' | grep -q '^visual-test$'; then
  gh label create "visual-test" \
    --description "Issues from automated visual regression tests" \
    --color "E06C75"
fi
if ! gh label list --json name --jq '.[].name' | grep -q '^run-visual-test$'; then
  gh label create "run-visual-test" \
    --description "Run deterministic visual regression workflow on a PR" \
    --color "E06C75"
fi

gh label list paginates with a default page size of 30 and --jq '.[].name' only emits the first page. The repo currently has 41 labels (verified via gh label list --limit 100 --json name --jq '.[] | .name' → 41 names). On the unflagged 30-item call, run-visual-test (position 41) is not in page 1, so:

  1. grep -q '^run-visual-test$' returns 1 (not found).
  2. The if ! body runs gh label create "run-visual-test" ....
  3. gh label create rejects the call with label with name "run-visual-test" already exists; use --force to update its color and description and exits 1.
  4. The step's run: propagates exit 1 to the GitHub Actions runner, marking the step as failed (X).
  5. All subsequent steps that rely on the default success() gating are skipped: Setup Lavapipe Vulkan, Run menu screenshot capture, Compare against golden image, Upload screenshot artifact, Upload build log artifact, Run opencode visual verification.
  6. Only if: always() (Check screenshot exists, Check build log exists) and if: failure() || steps.screenshot.outcome == 'failure' || steps.check_screenshot.outputs.screenshot_exists != 'true' (Run opencode failure diagnosis) still execute, which is why this triage step is running at all.
  7. weston.log survives because Stop headless Wayland compositor is also if: always(); it shows only the compositor startup that the parallel teardown step SIGTERMed.

This is the same failure that has been hitting the visual-test workflow every day since at least 2026-07-12 — see #916, #931, #935, #942, #944, #945, #946, #947, #949, #950, #952, #954. The most recent duplicates are #954 (run #30248571304) and #952 (run #30192579914), which document the identical root cause for the previous two schedule runs. Three consecutive days of open duplicates confirm the preflight logic is broken on every schedule run as soon as run-visual-test falls off page 1; the only thing that has kept the failure from firing more often is that some runs skip the preflight or gh happens to return the missing label by chance.

Origin of the failure

  • File: .github/workflows/visual-test.yml
  • Step: Ensure visual-test label exists (lines 55-68)
  • Command: gh label list --json name --jq '.[].name' (default --limit 30; first page only)
  • Condition: if ! ... | grep -q '^run-visual-test$'; then gh label create ...; fi — recreates an already-existing label once it falls off page 1
  • Head commit on this run: 8d205ff ("build: migrate dev environment and CI from nix flake to devenv (build: migrate dev environment and CI from nix flake to devenv #951)"), so the Ensure visual-test label exists step is the unmodified block that shipped in the migration.

Suggested fix

Either fetch all pages when listing labels, or make gh label create failures non-fatal so a duplicate label never aborts the workflow:

if ! gh label list --json name --jq '.[].name' --paginate | grep -q '^visual-test$'; then
  gh label create "visual-test" \
    --description "Issues from automated visual regression tests" \
    --color "E06C75" || true
fi
if ! gh label list --json name --jq '.[].name' --paginate | grep -q '^run-visual-test$'; then
  gh label create "run-visual-test" \
    --description "Run deterministic visual regression workflow on a PR" \
    --color "E06C75" || true
fi

gh label create --force ... is the equivalent one-liner if you want the existing labels' colours / descriptions refreshed. Either change stops the failure from blocking Setup Lavapipe Vulkan and Run menu screenshot capture, which is the actual signal we need from this workflow.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingdocumentationImprovements or additions to documentationhotfixquestionFurther information is requestedvisual-testIssues from automated visual regression tests

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions