Flaky: TestYtDlpSearchSkipsUnparseableLines failed once in a full make test run #146
Open
opened 2026-08-20 00:26:43 +00:00 by logan
·
3 comments
No Branch/Tag Specified
main
fix/146-stub-etxtbsy
fix/175-wizard-follows-the-library
fix/231-setter-rollback
fix/197-duplicate-column-label
docs/225-fixtures-wav-tags
docs/220-skill-check-scope
test/217-fixture-names-in-queue-selection
fix/216-riff-parse-allocation
fix/170-queue-header-action-names
fix/210-nav-sheet-scroll-affordance
docs/50-readme-landing-page
feat/65-art-prefetch-ahead
feat/71-more-as-a-bottom-sheet
feat/54-native-touch-feel
feat/67-entity-links-into-menus
test/196-visual-tier-gates
fix/138-ui-test-storage-leak
fix/104-wav-tags-read
fix/207-sheet-scroll-affordance
fix/204-ui-visual-update-filter
pi-agent-backlog-automation
63-touch-model-phase-2
63-android-touch-model
186-touch-targets-settings
186-touch-targets-page-header
187-seek-bar-hit-area
189-190-explore-correctness
135-android-underrun-instrumentation
51-android-small-screens
fix/171-phone-queue-scrim
fix/137-touch-only-affordances
fix/154-nested-css-check
feat/58-mini-player-progress-line
fix/66-album-page-scrolls-as-one
60-context-menu-action-sheet
64-android-system-volume
59-slim-the-mini-player
55-queue-as-a-screen
feat/57-drop-the-android-top-bar
feat/62-jobs-as-a-notification
fix/53-seek-bar-never-moves
fix/159-android-task-app-id
fix/52-android-activity-recreation-restarts-the-process
fix/150-expand-button-under-the-art
feat/42-inline-volume-and-centred-transport
fix/156-queue-selection-fixture-order
fix/151-fuse-the-scroll-guard-and-the-write
fix/43-queue-panel-selection
fix/143-top-bar-fits-its-window
feat/27-jobs-into-settings
feat/25-configurable-sidebar-tabs
feat/6-global-back-forward
fix/72-active-view-broadcast
fix/69-page-header-action-overflow
fix/quick-wins-batch
fix/118-in-library-clear
fix/61-mini-player-plain-text
fix/68-hover-affordances-pointer
fix/119-dev-headless-port
fix/130-issue-claim-user
fix/131-codegen-check-scope
feat/28-autotag-match-on-album
feat/17-demote-version-selector
feat/38-ownership-visibility
ci/115-manual-release
feat/34-icon-language
feat/7-full-tracklist-toggle
fix/16-tagwriter-totals
fix/unclaim-ca-certs
fix/unclaim-shell
ci/unclaim-on-close
docs/closing-keyword
docs/retire-stale-planning-docs
docs/issue-driven-workflow
integration/small-fixes
fix/small-issue-batch
fix/queue-toggle-state
fix/drag-count-badge
fix/album-card-year
fix/album-tracklist-heading
fix/seek-bar-clock-width
fix/explore-art-scanner-requests
chore/workflow-guardrails
v0.7.0
v0.6.0
v0.5.0
v0.4.0
v0.3.1
v0.3.0
v0.2.3
v0.2.2
v0.2.1
v0.2.0
v0.1.0
v0.0.1
v0.0.0
Labels
Clear labels
Area/Design
Area/Downloads
Area/Explore
Area/Library-UI
Area/Metadata
Area/Packaging
Area/Player
Area/Queue
Area/Settings
Area/Shell-Nav
Compat/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Platform/Android
Platform/Desktop
Breaking change that won't be backward compatible
Something is not working
Documentation changes
Improve existing functionality
New functionality
This is security issue
Issue or pull request related to testing
Priority
Critical
1
The priority is critical
Priority
High
2
The priority is high
Priority
Medium
3
The priority is medium
Priority
Low
4
The priority is low
Reviewed
Confirmed
1
Issue has been confirmed
Reviewed
Duplicate
2
This issue or pull request already exists
Reviewed
Invalid
3
Invalid issue
Reviewed
Won't Fix
3
This issue won't be fixed
Status
Blocked
1
Something is blocking this issue or pull request
Status
Need More Info
2
Feedback is required to reproduce issue or to continue work
Status
Abandoned
3
Somebody has started to work on this but abandoned work
Status
In Progress
Somebody is actively working on this right now
Milestone
No items
No Milestone
Projects
Clear projects
No projects
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: yonlu/yellowjacket#146
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.
Report
TestYtDlpSearchSkipsUnparseableLinesfailed once inside a fullmake testrun onfeat/27-jobs-into-settings, on a tree whose difftouches only
backend/configand the frontend:It has not reproduced since: 6 runs of
-run TestYtDlp -race -count=1,4 runs of the whole package with
-race, and a second fullmake testall pass. It also passes on a stashed (pristine) tree.
Findings
t.Parallel()and drivesnewStubYtDlp, which writes astub shell script and runs it as an external process — so it depends
on a fork/exec and on the filesystem, which is the usual shape of a
test that fails once under load. The machine was running the Vitest
browser suite and a headless app at the time.
Searchis called withcontext.Background(), so a timeout is notthe mechanism; whatever failed, failed inside the stub or its exec.
t.Fatalfs — aSearcherror, or a candidate count other than 2. The output wascaptured only as the summary line, so which one is not known.
Direction
Not worth chasing blind. What is worth doing is making the next
occurrence self-describing: the failure gave no message beyond the test
name, because the interesting values (
err,len(got), the stub'sstderr) are not in the
Fatalfs. Include the stub's combined output inthe error text, then leave it until it recurs.
Filed rather than mentioned in passing because a once-in-a-run failure
in a package nobody is touching is exactly what gets attributed to
whatever change happens to be in flight — this one was not it, and #140
and #138 are already two of those.
Reproduced in CI, with the message this issue asked for.
Run 18244's first attempt (job
check, commit26251baondocs/220-skill-check-scope, a diff containing no Go at all):A re-run of the same commit passed, so it is the flake this issue
describes and not a regression.
So it is the
Searcherror branch, and the mechanism is ETXTBSY onthe exec, not anything inside the stub. "text file busy" is the
kernel refusing to exec a file that is open for writing anywhere in the
process — the classic shape is that
newStubYtDlpwrites and closesthe script, and a concurrent
forkin another goroutine duplicatesthat still-open write descriptor into a child that outlives the close,
so the exec a moment later sees a writer.
t.Parallel()plus a packagewhose tests each write and exec their own stub is exactly the
population needed, which is also why it only appears under a loaded full
run and never under
-run TestYtDlp.Two consequences for the Direction already on this issue:
Fatalfwould not have helped here — the stub never ran. What identified it
was the wrapped
err, whichprovider_ytdlp_test.go:178alreadyprints; the original report simply captured only the summary line.
side: close the file before any sibling can fork (or write the stub
once per package into a shared dir, rather than once per test), which
is a change to
newStubYtDlpand not to the test that happens tolose the race.
O_CLOEXECalone does not close it, since the windowis between another goroutine's fork and its exec.
Still
Priority/Lowfrom here: it costs a re-run, not a wrong answer.Picking this up on
fix/146-stub-etxtbsy.The mechanism reproduces on demand, which is what makes it takeable
rather than a blind chase. A standalone harness — 12 goroutines, each
writing an executable script into its own temp dir and exec'ing it,
which is
stubYtDlp's exact shape — produces 271–302 ETXTBSY failuresin 4800 execs, in under a second, on this machine.
And the fix is on the writing side, as the comment above predicted,
though neither of the two options named there is the one that works.
Closing sooner is not available:
os.WriteFilehas already closed thefile before the exec, and the descriptor that makes the kernel refuse is
a copy taken by a sibling's
forkwhile it was briefly open. Writingthe stub once per package into a shared dir narrows the window but does
not remove it — the first write of each script still races.
What removes it is holding
syscall.ForkLockacross the write. That isthe lock
syscall.forkExectakes exclusively around the fork, so nochild can be created while our write descriptor exists, and a child
forked before the write cannot hold a descriptor for a file that did not
yet exist. Measured on the same harness: 0 of 4800, three runs.
Change is confined to
stubYtDlpinbackend/download/provider_ytdlp_test.go— it is the only test helper in the repo that writes an executable and
then execs it (
cmd/indexbuild/deps_test.goexecsgo list).PR: #235 — CI green
(run 18482,
checkande2eboth success).The fix is
syscall.ForkLockheld across the write instubYtDlp,which is on the writing side as this issue's comment directed, though it
is neither of the two options named there: closing sooner is not
available (
os.WriteFilehas already closed the file before anythingexecs it — the offending descriptor is a copy taken by a fork already
in flight), and a shared per-package dir narrows the window rather than
removing it, since the first write of each script still races.
Measured on the helper itself, 12 concurrent writers, 2400 execs:
189 / 176 / 178 ETXTBSY before, 0 / 0 / 0 after. The probe that
produced those numbers is deliberately not committed — a stress test
that only sometimes fails is the class of thing this issue is about.
The original failure is still not reproducible as a failure; what is
now impossible is the mechanism, so a recurrence would mean a different
cause and would deserve its own issue.