scripts/e2e-local.sh readiness probe hangs on a reused config dir #533
Closed
opened 2026-07-21 17:31:12 +02:00 by timothy
·
3 comments
No Branch/Tag Specified
main
renovate/lucene.net
renovate/dotnet-monorepo
renovate/sqlitepclraw.bundle_e_sqlite3-3.x
renovate/meziantou.analyzer-3.x
renovate/cliwrap-3.x
913-scripted-playout-401
release/v26.15.0-notes
issue-806-guard-populations
scratch/767b-poisoned
scratch/767b-control
release/v26.14.0-notes
release/v26.14.0
docs/510-skill-logo-bug-policy
fix/510-watermark-resolution-policy
fix/629-verdict-classifier-falseopens
fix/609-decisions-edit-token-scope
issue-135-clear-to-none
release/v26.12.0-notes
fix/409b-lastscan-api-parity
fix/401-updatechannel-mirror-422
fix/327-playlist-rename-validation
fix/410-scancancel-log-level
fix/409-447-librariesscreen-neverscanned
fix/338-zap-exit-code
fix/367-plex-budget-message
fix/310-debom-legacy-cs
ci/604-lane-rebalance
feat/388-design-mirror
feat/247-test-ownership
feat/247-primary-action
feat/357-player-owned-playback
feat/357-jellyfin-plugin-poc
fix/289-mcp-hardening
issue58-mcp
feat/244-channels-extract
ci/auto-bump-prod-compose
feat/multi-rerun-collections-api
feat/collections-api
feat/quick-wins
feat/185-docs-part2
feat/140-collections-screen
feat/146-channel-edit
feat/147-classic-ui-link
issue22-renovate-dashboard
feat/91-cutover
feat/63-composite-create
feat/65-library-browse
feat/85-epg
feat/86-schedule-editor
feat/109-dashboard-data
feat/99-session-tracking
fix/dockerfile-node-tag
feat/59-spa-foundation
docs/59-ui-redesign-brief
feat/102-json-guide
feat/111-schedule-durations
feat/104-artwork-upload
feat/103-media-sources-api
feat/playouts-read-api
feat/108-health-api
feat/105-picker-list-endpoints
issue-97-channel-state-api
issue42-jellyfin-musicvideos
issue46-rest-api-error-contract
dependabot/nuget/ErsatzTV.FFmpeg.Tests/multi-d307a2e06f
qsv-improvements
hdr-vulkan-cuda-test
v26.16.0
v26.15.0
v26.14.0
v26.13.0
v26.12.0
v26.11.0
v26.10.0
v26.9.0
v26.8.0
v26.7.0
blazor-final
v26.6.0
v26.5.0
v26.4.0
v26.3.1
v26.3.0
v26.2.0
v26.1.1
v26.1.0
v25.9.0
v25.8.0
v25.7.1
v25.7.0
v25.6.0
v25.5.0
v25.4.0
v25.3.1
v25.3.0
v25.2.0
v25.1.0
v0.8.8-beta
v0.8.7-beta
v0.8.6-beta
v0.8.5-beta
v0.8.4-beta
v0.8.3-beta
v0.8.2-beta
v0.8.1-beta
v0.8.0-beta
v0.7.9-beta
v0.7.8-beta
v0.7.7-beta
v0.7.6-beta
v0.7.5-beta
v0.7.4-beta
v0.7.3-beta
v0.7.2-beta
v0.7.1-beta
v0.7.0-beta
v0.6.9-beta
v0.6.8-beta
v0.6.7-beta
v0.6.6-beta
v0.6.5-beta
v0.6.4-beta
v0.6.3-beta
v0.6.2-beta
v0.6.1-beta
v0.6.0-beta
v0.5.8-beta
v0.5.7-beta
v0.5.6-beta
v0.5.5-beta
v0.5.4-beta
v0.5.3-beta
v0.5.2-beta
v0.5.1-beta
v0.5.0-beta
v0.4.5-alpha
v0.4.4-alpha
v0.4.3-alpha
v0.4.2-alpha
v0.4.1-alpha
v0.4.0-alpha
v0.3.8-alpha
v0.3.7-alpha
develop
v0.3.6-alpha
v0.3.5-alpha
v0.3.4-alpha
v0.3.3-alpha
v0.3.2-alpha
v0.3.1-alpha
v0.3.0-alpha
v0.2.5-alpha
v0.2.4-alpha
v0.2.3-alpha
v0.2.2-alpha
v0.2.1-alpha
v0.2.0-alpha
v0.1.5-alpha
v0.1.4-alpha
v0.1.3-alpha
v0.1.2-alpha
v0.1.1-alpha
v0.1.0-alpha
v0.0.62-alpha
v0.0.61-alpha
v0.0.60-alpha
v0.0.59-alpha
v0.0.58-alpha
v0.0.57-alpha
v0.0.56-alpha
v0.0.55-alpha
v0.0.54-alpha
v0.0.53-alpha
v0.0.52-alpha
v0.0.51-alpha
v0.0.50-alpha
v0.0.49-prealpha
v0.0.48-prealpha
v0.0.47-prealpha
v0.0.46-prealpha
v0.0.45-prealpha
v0.0.44-prealpha
v0.0.43-prealpha
v0.0.42-prealpha
v0.0.41-prealpha
v0.0.40-prealpha
v0.0.39-prealpha
v0.0.38-prealpha
v0.0.37-prealpha
v0.0.36-prealpha
v0.0.35-prealpha
v0.0.34-prealpha
v0.0.33-prealpha
v0.0.32-prealpha
v0.0.31-prealpha
v0.0.30-prealpha
v0.0.29-prealpha
v0.0.28-prealpha
v0.0.27-prealpha
v0.0.26-prealpha
v0.0.25-prealpha
v0.0.24-prealpha
v0.0.23-prealpha
v0.0.22-prealpha
v0.0.21-prealpha
v0.0.20-prealpha
v0.0.19-prealpha
v0.0.18-prealpha
v0.0.17-prealpha
v0.0.16-prealpha
v0.0.15-prealpha
v0.0.14-prealpha
v0.0.13-prealpha
v0.0.12-prealpha
v0.0.11-prealpha
v0.0.10-prealpha
v0.0.9-prealpha
v0.0.8-prealpha
v0.0.7-prealpha
v0.0.6-prealpha
v0.0.5-prealpha
v0.0.4-prealpha
v0.0.3-prealpha
v0.0.2-prealpha
v0.0.1-prealpha
Labels
Clear labels
ad-hoc
api
bug
ci-cd
content
dependencies
enhancement
frontend
in-progress
jellyfin
parked
priority: high
priority: low
priority: medium
review
security
One-off / ad-hoc work not tracked by a dedicated issue
REST API / HTTP endpoints
Something isn't working
Build, test, deploy pipeline
Channel content / schedules / playlists
Dependency updates (Renovate)
New feature or improvement
ChicoryTV React SPA frontend
Claimed by an active session — do not pick up
Jellyfin tuner / IPTV integration
Excluded from automatic queue pickup; work only when explicitly selected
Adversarial review finding
Security / vulnerability fix
No labels
priority: low
Milestone
No items
No Milestone
Projects
Clear projects
No projects
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: timothy/ersatztv#533
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Problem
scripts/e2e-local.shwaits for a single fixed readiness line:RebuildSearchIndexHandleremits that line only when the search index actually migrates — i.e. on afresh config dir. A reused config dir logs
Search index is already version {Version}instead,so
READY_LINEnever appears, the probe times out, and the script kills a perfectly healthy server.docs/e2e-local.mddocuments the ready line and recommends a fresh config dir, but does not documentthis failure mode. It cost a session two failed launches before the cause was found.
Fix
Accept either line:
Provenance
Surfaced during the #502 session, recorded only in a comment on the closed tracker #237, and
recovered by the #524 triage — it was one of exactly two facts in #237 with no home anywhere else.
Done-when
docs/e2e-local.mddocuments the reuse path.Resolved in PR #591 (
scripts/e2e-local.sh,docs/e2e-local.md).Verified with a negative control rather than just asserting the regex matches:
Done migrating search index in 482 millisecondsSearch index is already version 50The control: the old probe string is genuinely absent from the reused-dir run's log, so the old code would have waited out the full 120s and killed the server. The fix is load-bearing, not incidentally passing.
Also confirmed the alternation is exhaustive by reading
RebuildSearchIndexHandler— the two lines come from anif/elseimmediately beforeSystemStartup.SearchIndexIsReady(), so there is no third path to readiness. And the other consumer of this script is unaffected:scripts/e2e-functional.shstill passes 45/45.Two things found while in here, both fixed in the same PR:
$!was the subshell, not the dotnet process, on bash 3.2 (stock macOS/bin/bash) because the script backgrounds a two-command subshell — so every PID-based kill and liveness check silently targeted the wrong process. Fixed withexec. (Measured: bash 3.2 → subshell; bash 5.3 → leaf; withexec→ leaf on both.)e2e-local.shinstances must never run concurrently, even on different ports: eachrm -rfs and re-copies the same build-outputwwwroot, so the second yanks the static files out from under a still-starting first. Now documented — the symptom (readiness timeout, or/app404ing) looks nothing like a shared-directory race.The "prefer a fresh config dir" guidance stays, on state-bleed grounds. The now-obsolete rationale in the
testing.e2e-local-fresh-config-dirdecision record was corrected in the same PR — it had still been asserting, as anactiverecord, that this bug existed.🔗 Session bundle — E2E / test-harness infra: #445, #533, #563
Test-harness infrastructure: #445 (UI-interactive Playwright headless flows for functional-E2E), #533 (e2e-local.sh readiness probe hangs on a reused config dir), #563 (scripted-playout integration harness deferred from #381). Sibling to the just-landed #381 golden work; all live in the E2E/test tooling.
When you claim any member, check this cluster for co-workable siblings and sweep the disjoint set in one session (per docs/handoffs/chicorytv-issue-queue.md → bundles).
Claiming — swept into this session alongside #445 (both members of the 🔗 E2E / test-harness bundle; #563 deferred to a later session as a second large lift).
Approach is the fix in the body: widen the readiness probe in
scripts/e2e-local.shto agrep -Eqalternation over both lines (Done migrating search indexon a fresh index migration,Search index is already versionon a reused config dir), and document the reuse path indocs/e2e-local.md. The "prefer a fresh config dir" recommendation stays — that guards against state bleed, which is a separate concern from the probe hanging.Will verify against both a fresh and a reused config dir (the Done-when criterion) rather than only asserting the regex matches.
Closing record
Outcome: Shipped in PR #591 (with #445, its bundle sibling).
scripts/e2e-local.sh's readiness probe now matches aREADY_REGEXalternation over both ready lines, so a reused config dir no longer times out and kills a healthy server. Two adjacent defects in the same script were fixed while in there (see Reusable knowledge).Root cause:
RebuildSearchIndexHandlerhas anif/elseimmediately beforeSystemStartup.SearchIndexIsReady()that logs one of two mutually-exclusive lines —Done migrating search indexonly when the index actually migrates (fresh config),Search index is already version {Version}when there is nothing to migrate (reused config). The probe hard-coded only the first. So the bug was not a race or a timing issue: the watched line is structurally unreachable on a reused dir, making the 120s timeout and the subsequent kill of a perfectly healthy server the guaranteed outcome. It read as a broken build, which is why it cost the #502 session two launches before anyone looked at the branch.Decisions/conventions changed: No new key.
testing.e2e-local-fresh-config-diramended — it was stillstatus: activewhile asserting this bug existed ("a reused one never emits the probe's ready line", "there is no 'wait for either line' option today", "widening the probe … is tracked as #533"), andREADY_LINEsurvived only in that record, nowhere in code. Amended rather than superseded because the operative Rule (use a fresh config dir) never reversed — only its rationale moved from "the probe hangs" to "state bleed" — and the key is cited fromdocs/handoffs/chicorytv-issue-queue.mdplus twodocs/superpowers/files that a supersession would have aimed at an archived record.Reusable knowledge:
$!can be the SUBSHELL, not the process you launched.( cd X; cmd ) &backgrounds a two-command subshell, and bash only collapses that to the leaf on newer versions. Measured: bash 3.2.57 (stock macOS/bin/bash, which#!/usr/bin/env bashpicks up without Homebrew bash on PATH) →$!is the subshell; bash 5.3 → leaf; withexec→ leaf on both. Consequence when it bites: every PID-basedkillandkill -0liveness check silently targets the wrong process, so cleanup "succeeds" while the real server keeps the port. Fix isexecin the subshell. Note a single-command subshell is collapsed on both versions — so a naive test of this reproduces nothing and wrongly clears the bug.e2e-local.shinstances must never run concurrently, even on different ports. Each runrm -rfs and re-copies the sameErsatzTV/bin/<config>/net10.0/wwwroot, so the second pulls the static files out from under a still-starting first. Symptom is a readiness timeout or/app404ing — nothing that suggests a shared-directory race. Found by accident when a badly-written verification of mine overlapped two instances.Verification: Fresh dir → ready in 5.4s via
Done migrating search index in 482 milliseconds; reused dir → ready in 2s viaSearch index is already version 50, with the old probe string confirmed absent (negative control). Other consumer unaffected:scripts/e2e-functional.sh45/45 PASS, re-checked after theexecchange. CI green on head7030bd28— all sixdocker-buildjobs plus all three PR gates.Deferred: none for this issue.
Docs updated:
docs/e2e-local.md(step-4 two-line readiness table + the never-run-concurrently warning +ETV_PIDFILE),docs/decisions/workflow-process.md(testing.e2e-local-fresh-config-diramended),docs/decisions/README.md(regenerated).