Skip to content

Commit bd3429c

Browse files
committed
fix(ci): gate gh-pages commit on e2e-install, add x-skip-launch-check to ghostty
Structural fix: update-index.yml now runs e2e-install BEFORE committing to gh-pages (prepare → e2e-install → commit-index). A failing launch check blocks the index commit — the stale/broken index is never published to the remote. Root cause fix: add x-skip-launch-check: true to ghostty manifest. GUI apps that require Wayland/X11 exit 1 immediately in headless CI (gnome-49 has no display). timeout 5 only produces exit 124 (PASS) if the app stays alive; instant exit 1 is treated as failure. ghostty always exits 1 headlessly. Documented in skills/app-gotchas.md under 'GUI apps — x-skip-launch-check'. Assisted-by: Claude Sonnet 4.6 via OpenCode
1 parent 08e59a4 commit bd3429c

File tree

3 files changed

+86
-26
lines changed

3 files changed

+86
-26
lines changed

.github/workflows/update-index.yml

Lines changed: 70 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,35 +3,40 @@ name: Sync Flatpak Remote Index
33
# Triggered after each "Build → Sign → Publish Flatpak OCI" run completes successfully.
44
# Running as a separate workflow serialises all gh-pages pushes — no race
55
# between concurrent app builds writing to the same branch.
6+
#
7+
# Job order enforces a strict gate:
8+
# prepare → e2e-install → commit-index
9+
#
10+
# The gh-pages index is only committed AFTER e2e-install passes.
11+
# This prevents the index from pointing at a broken image that cannot be installed.
612

713
on:
814
workflow_run:
915
workflows: ["Build → Sign → Publish Flatpak OCI"]
1016
types: [completed]
1117

12-
# Top-level: deny all. This job only needs to write gh-pages and read packages.
18+
# Top-level: deny all. Jobs request only what they need.
1319
permissions: {}
1420

1521
jobs:
16-
update-index:
22+
# ── Prepare: build updated index content and derive e2e matrix ────────────
23+
# Downloads per-arch digest artifacts from the triggering build run,
24+
# runs update-index.py against ghcr.io, and uploads the result as an artifact.
25+
# Does NOT commit to gh-pages yet — that happens only after e2e-install passes.
26+
prepare:
1727
if: >-
1828
github.event.workflow_run.conclusion == 'success' &&
1929
(github.event.workflow_run.event == 'push' ||
2030
github.event.workflow_run.event == 'merge_group' ||
2131
github.event.workflow_run.event == 'workflow_dispatch')
2232
runs-on: ubuntu-24.04
2333
permissions:
24-
contents: write
34+
contents: read
2535
packages: read
2636
actions: read
2737
outputs:
2838
matrix: ${{ steps.matrix.outputs.matrix }}
2939
has_entries: ${{ steps.matrix.outputs.has_entries }}
30-
# Serialise gh-pages pushes — prevents race if two workflow_run events fire
31-
# before the first has committed.
32-
concurrency:
33-
group: gh-pages-update
34-
cancel-in-progress: false
3540
steps:
3641
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
3742
with:
@@ -85,18 +90,12 @@ jobs:
8590
--tags "latest"
8691
done
8792
88-
- name: Commit and push index
89-
run: |
90-
cd pages
91-
git config user.name "github-actions[bot]"
92-
git config user.email "github-actions[bot]@users.noreply.github.com"
93-
git add index/static
94-
if git diff --cached --quiet; then
95-
echo "index unchanged, skipping commit"
96-
else
97-
git commit -m "chore(index): update from build ${{ github.event.workflow_run.run_number }} [skip ci]"
98-
git push
99-
fi
93+
- name: Upload prepared index as artifact
94+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
95+
with:
96+
name: prepared-index-${{ github.run_id }}
97+
path: pages/index/static
98+
retention-days: 1
10099

101100
- name: Build app+arch matrix from digest artifacts
102101
id: matrix
@@ -123,13 +122,15 @@ jobs:
123122
echo "matrix=${MATRIX}" >> "$GITHUB_OUTPUT"
124123
echo "has_entries=$(echo "${MATRIX}" | python3 -c 'import sys,json; d=json.load(sys.stdin); print("true" if d.get("include") else "false")')" >> "$GITHUB_OUTPUT"
125124
126-
# ── E2E install test (runs AFTER gh-pages index is fresh) ─────────────────
125+
# ── E2E install test ──────────────────────────────────────────────────────
126+
# Gate: runs BEFORE gh-pages is updated. The index is only committed if this passes.
127+
# Installs from ghcr.io directly (images are live there regardless of gh-pages state).
128+
#
127129
# Launch check: exit 0 = clean exit (PASS), exit 124 = timeout/headless GUI (PASS), other = FAIL
128-
# Runs AFTER update-index so the install test always uses the freshly-pushed image from
129-
# the live gh-pages index — eliminates the stale-digest class of bugs.
130+
# Apps that cannot launch headless must set x-skip-launch-check: true in their manifest.
130131
e2e-install:
131-
needs: [update-index]
132-
if: needs.update-index.outputs.has_entries == 'true'
132+
needs: [prepare]
133+
if: needs.prepare.outputs.has_entries == 'true'
133134
runs-on: ${{ matrix.arch == 'aarch64' && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
134135
permissions:
135136
packages: read
@@ -138,7 +139,7 @@ jobs:
138139
options: --privileged
139140
strategy:
140141
fail-fast: false
141-
matrix: ${{ fromJson(needs.update-index.outputs.matrix) }}
142+
matrix: ${{ fromJson(needs.prepare.outputs.matrix) }}
142143
steps:
143144
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
144145

@@ -159,3 +160,46 @@ jobs:
159160
run: |
160161
APP_ID=$(just metadata '${{ matrix.app }}' app-id 2>/dev/null || echo "")
161162
[[ -n "${APP_ID}" ]] && flatpak uninstall --system --noninteractive "${APP_ID}" || true
163+
164+
# ── Commit index to gh-pages ──────────────────────────────────────────────
165+
# Only runs after e2e-install passes — the gate is enforced by needs: [e2e-install].
166+
# Downloads the prepared index artifact from the prepare job and commits it.
167+
# Serialised via concurrency group to prevent races between concurrent builds.
168+
commit-index:
169+
needs: [prepare, e2e-install]
170+
if: |
171+
always() &&
172+
needs.prepare.result == 'success' &&
173+
needs.e2e-install.result == 'success'
174+
runs-on: ubuntu-24.04
175+
permissions:
176+
contents: write
177+
actions: read
178+
concurrency:
179+
group: gh-pages-update
180+
cancel-in-progress: false
181+
steps:
182+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
183+
with:
184+
ref: gh-pages
185+
186+
- name: Sync gh-pages before writing
187+
run: git fetch origin gh-pages && git rebase origin/gh-pages
188+
189+
- name: Download prepared index artifact
190+
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
191+
with:
192+
name: prepared-index-${{ github.run_id }}
193+
path: index/static
194+
195+
- name: Commit and push index
196+
run: |
197+
git config user.name "github-actions[bot]"
198+
git config user.email "github-actions[bot]@users.noreply.github.com"
199+
git add index/static
200+
if git diff --cached --quiet; then
201+
echo "index unchanged, skipping commit"
202+
else
203+
git commit -m "chore(index): update from build ${{ github.event.workflow_run.run_number }} [skip ci]"
204+
git push
205+
fi

flatpaks/ghostty/manifest.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ runtime-version: "49"
44
sdk: org.gnome.Sdk
55
default-branch: stable
66
x-chunkah-max-layers: "8"
7+
x-skip-launch-check: true # GUI app: requires Wayland/X11 display; exits 1 in headless CI
78
command: ghostty
89
finish-args:
910
- --device=all # Required: GPU access (Vulkan/Metal) and PTY device access for terminal

skills/app-gotchas.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,18 @@ not appear in the installed Flatpak unless the upstream bundle already includes
145145

146146
Applies to: **goose** (bundle-repack path). All other apps use `manifest.yaml`
147147
(flatpak-builder) and install metainfo directly.
148+
149+
## GUI apps — x-skip-launch-check required
150+
151+
Any app that requires a Wayland or X11 display will exit 1 immediately in headless CI (gnome-49 container has no display server). The `timeout 5 flatpak run` launch check treats exit 1 as FAIL, not as PASS. It only passes on exit 0 (clean self-exit) or exit 124 (timeout — app stayed running).
152+
153+
**Symptom:** `e2e-install` fails with `WARNING: Gtk: Failed to open display` / `ERROR: <app-id> launch failed (exit 1)`.
154+
155+
**Fix:** add `x-skip-launch-check: true` to the app's `manifest.yaml` (or `release.yaml`).
156+
157+
Apps that need this flag: any GTK, Qt, Electron, or other GUI app that opens a window on startup. CLI apps and apps with `--version`/`--help` exit paths do not need it.
158+
159+
**Structural safeguard:** `update-index.yml` runs `e2e-install` before committing to gh-pages (`prepare → e2e-install → commit-index`). A failing launch check blocks the index commit — the stale index is never published. But fixing `x-skip-launch-check` is still required; the structural gate is a last resort, not a substitute.
160+
161+
**Known apps with this flag:**
162+
- `ghostty` — GTK4/Wayland terminal, exits 1 with "Failed to open display" in CI

0 commit comments

Comments
 (0)