Skip to content

fix(publish): S_IFREG on scripts + per-wheel verification + 2.1.21 bump#143

Merged
zackees merged 1 commit intomainfrom
fix/publish-script
Apr 19, 2026
Merged

fix(publish): S_IFREG on scripts + per-wheel verification + 2.1.21 bump#143
zackees merged 1 commit intomainfrom
fix/publish-script

Conversation

@zackees
Copy link
Copy Markdown
Member

@zackees zackees commented Apr 19, 2026

Summary

Three tightly-coupled publish-pipeline fixes forced out by 2.1.20 shipping with broken Linux/macOS wheels.

1. fix(publish): set S_IFREG on wheel script entries

2.1.20 shipped with external_attr=0x01ed0000 on fbuild / fbuild-daemon script entries: mode bits correctly set to 0o755 but the file-type bit (S_IFREG, 0o100000) was zero. pip's wheel installer calls stat.S_ISREG() on the upper 16 bits before applying script permissions; without IFREG, that test returns False, pip falls back to umask defaults (0o644), and the binary lands without +x:

/opt/hostedtoolcache/Python/3.12.13/x64/bin/fbuild: Permission denied (exit 126)

Windows .exe files don't need an exec bit, which is why 2.1.20 looked fine via uv tool install fbuild==2.1.20 on Windows but was broken for every Linux/macOS pip install fbuild. This is also the persistent root cause behind #129#135's "preserve exec bit" fix set the mode but not the file-type bit, so #129 reproduced in 2.1.18, 2.1.19, 2.1.20.

Reference: uv, ruff, maturin-built wheels all ship script entries with external_attr=0x81ed0000 (S_IFREG | 0o755), not 0x01ed0000.

Verified locally by rebuilding the Linux x86_64 wheel against the existing binary artifact: new external_attr=0x81ed0000, IFREG=True, matching uv byte-for-byte in the file-type and mode bits.

2. fix(publish): fail loudly on partial releases (original scope of this PR)

Concurrent per-wheel upload + pre/post verification + --upload-only flag. 2.1.20 had already shipped with only 2 of 4 wheels on PyPI before the S_IFREG bug was found — this fixes the publish-script side of the damage. See commit message for detail.

3. chore: bump version to 2.1.21

2.1.20 is now stuck on PyPI — its filenames are taken, PyPI does not allow overwriting. A fresh version is the only way to ship the S_IFREG fix to end-users. pyproject.toml, Cargo.toml (workspace), uv.lock, Cargo.lock all bumped to 2.1.21.

Post-merge publish plan

After this PR lands on main:

git checkout main && git pull
./publish   # exercises the fixed script; Step 6 verifies all 4 wheels land on PyPI

Then validate end-to-end on Linux by pointing FastLED's benchmark at fbuild==2.1.21 and confirming /bin/fbuild is executable.

Test plan

  • Inspected 2.1.20 wheels on disk: all 4 have external_attr=0x01ed0000, IFREG=False. Confirms the bug ships in every platform wheel.
  • Compared to uv-0.11.7 Linux wheel: script entries have external_attr=0x81ed0000, IFREG=True.
  • Rebuilt the Linux x86_64 wheel locally against the new publish.py: script entries now show external_attr=0x81ed0000, IFREG=True.
  • ./publish --upload-only already battle-tested at the end of 2.1.20's partial-release cleanup.
  • ./publish from main post-merge exercises Steps 2-6 end-to-end against a fresh version.
  • FastLED benchmark ([META] Fastest possible FastLED examples CI rebuild — profile + benchmark #112 bench/fastled-examples iter2) — currently failing with Permission denied on fbuild==2.1.20; repoint to 2.1.21 and confirm the compile phase runs.

🤖 Generated with Claude Code

v2.1.20 shipped to PyPI with only 2 of 4 wheels (missing
win_amd64 and manylinux_x86_64) because the upload step did not
verify that every expected platform actually made it through.

- download step now hard-fails on any missing artifact instead of
  warning
- wheel-build step asserts a wheel was produced for every platform
- upload step runs one `uv publish --check-url <pypi>/simple/ <whl>`
  per wheel concurrently, so per-file failures surface instead of
  being swallowed by a batched publish
- post-upload verification queries PyPI and asserts every expected
  filename is present; hard-fails with bump instructions otherwise
- new --upload-only flag retries uploads against wheels already in
  dist/wheels/ (--check-url makes it idempotent); this is how 2.1.20
  was repaired without a version bump

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 19, 2026

Warning

Rate limit exceeded

@zackees has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 28 minutes and 3 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 28 minutes and 3 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f2dfd23c-a31f-4ef9-a2a9-a0c8490e0927

📥 Commits

Reviewing files that changed from the base of the PR and between 3452945 and 37e6e49.

📒 Files selected for processing (1)
  • ci/publish.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/publish-script

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@zackees zackees merged commit 27f9f29 into main Apr 19, 2026
76 of 77 checks passed
@zackees zackees changed the title fix(publish): fail loudly on partial releases fix(publish): S_IFREG on scripts + per-wheel verification + 2.1.21 bump Apr 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant