Native Package Lifecycle has been failing on PRs that touch packaging. This is the cause. It is not the fault of any of those PRs.
What actually happens
run: bun test packages/typescript/src/package.test.tsbun reads that argument as a filter, not a path, and walks the repository looking for test files that match. On a hoisted node_modules the walk runs out of file descriptors before it finds anything:
error: Cannot read file "/Users/runner/work/craft/craft/packages/typescript": EMFILE
error: Cannot read file "/Users/runner/work/craft/craft/packages/typescript/tsconfig.json": EMFILE
##[error]Process completed with exit code 1The fix is ./:
run: bun test ./packages/typescript/src/package.test.tsMeasured at a range of limits — the filter form needs more than 512 descriptors, the path form runs the same 19 tests in 64:
ulimit -n | bun test packages/… | bun test ./packages/… |
|---|---|---|
| 64 | fail | pass |
| 128 | fail | pass |
| 256 | fail | pass |
| 512 | pass | pass |
Why it took two attempts
My first fix was ulimit -n 4096, on the theory that the runner starts a step at 256 and the test genuinely needs ~500. That was wrong, and CI said so: the step ran with 4096 and failed anyway. Raising the soft limit does not stop the repository walk from exceeding what the process can actually have. Ruling it out is what led to the filter-vs-path difference.
Why it was hard to read
Two signals, both pointing away:
Cannot read file … tsconfig.jsonreads as a missing file, not a resource limit.- The only job-level symptom is two steps later —
No files were found with the provided path: artifacts/native-lifecycle/…— which points squarely at the artifact path, i.e. the most recently changed thing in that workflow, and entirely innocent.
It also failed intermittently rather than always, which cost a wasted rerun before I stopped calling it flaky: whether the walk trips depends on how much of node_modules is already open, so a cache-hit bun install (Checked 397 installs … (no changes)) survives where a fresh install of 351 packages does not.
bun test --coverage in ci.yml takes no filter argument and is unaffected; it is the only other bun test in the workflows.