ci: per-platform PyInstaller build + release workflow (#33)#63
Conversation
Adds .github/workflows/release-binaries.yml — a four-target matrix (linux-x64, macos-x64, macos-arm64, windows-x64) that runs PyInstaller on v* tags and attaches the binaries plus a SHA-256 manifest to the GitHub Release. Manifest contract (locked for downstream consumers #42, #43): - Filename: SHA256SUMS.txt - Format: standard sha256sum-compatible - Binary names: wbox-<platform>[.exe] The existing publish (PyPI) job in ci.yml is unchanged. Closes #33 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 79def36bc3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| pyinstaller \ | ||
| --onefile \ | ||
| --name "${{ matrix.binary }}" \ | ||
| --noconfirm \ | ||
| scripts/wbox_entry.py |
There was a problem hiding this comment.
Bundle package data into PyInstaller binaries
For the released binaries, commands such as wbox skills install and wbox skills upgrade load wealthbox_tools/skills/wealthbox-crm via importlib.resources, but this PyInstaller invocation only includes the Python entry script/modules. PyInstaller’s documented options require adding non-code assets explicitly (--add-data) or collecting package data (--collect-data, “Collect all data from the specified package or module”), so the --version smoke test can pass while the binary omits the skill template and later fails when users run those commands. Please add the bundled skill data to both PyInstaller invocations and smoke-test a resource-backed command.
Useful? React with 👍 / 👎.
) Codex auto-review P2: --version smoke test passes even if the binary omits skills/wealthbox-crm, so users only discover the missing template when running `wbox skills install` or `wbox skills upgrade`. Both load wealthbox_tools/skills/wealthbox-crm via importlib.resources, which PyInstaller does not bundle by default for non-Python assets. Add --collect-data wealthbox_tools to both PyInstaller invocations (Unix and Windows) so the entire skills/ tree is captured. Extend the smoke test to invoke `skills install --platform claude-code-user --no-bootstrap`, which exercises the bundled resource and fails the build if --collect-data didn't pull the template in.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 03606819b3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| target: linux-x64 | ||
| binary: wbox-linux-x64 | ||
| shell: bash | ||
| - os: macos-13 # Intel runner (x86_64) |
There was a problem hiding this comment.
Use a supported Intel macOS runner
This x64 build now targets macos-13, but GitHub’s Actions macOS 13 deprecation notice says the macos-13 label was retired in December 2025 and recommends labels such as macos-15-intel for x86_64 workloads. On current tag releases this matrix leg will never be scheduled, so the required build job fails and manifest-and-release cannot publish any binaries.
Useful? React with 👍 / 👎.
GitHub Actions retired the macos-13 label in December 2025 and recommends macos-15-intel for x86_64 workloads going forward. With macos-13 the x64 matrix leg would never schedule on a real v* tag, failing the required build job and blocking manifest-and-release from publishing any binaries. Critical Codex P1 — fixed in PR rather than deferred to an issue because this PR's entire purpose is producing release binaries; without a working x64 macOS leg the workflow has zero release value.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cd8d0c9faf
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| --collect-data wealthbox_tools \ | ||
| scripts/wbox_entry.py |
There was a problem hiding this comment.
Copy package metadata into the frozen binary
For these PyInstaller builds, the smoke test invokes --version, which calls importlib.metadata.version("wealthbox-cli") in src/wealthbox_tools/cli/main.py; PyInstaller’s docs state that distribution metadata is not collected by default and must be added with --copy-metadata/copy_metadata, while --collect-data wealthbox_tools only collects package data. As a result, every matrix binary can build successfully but then fail immediately at ./dist/${{ matrix.binary }} --version with PackageNotFoundError, preventing release assets from being published; add --copy-metadata wealthbox-cli to both PyInstaller invocations.
Useful? React with 👍 / 👎.
…es (PR #63) PyInstaller's --collect-data only collects package data files; it does NOT copy distribution metadata. Five modules call importlib.metadata.version ("wealthbox-cli") (cli/main.py, cli/doctor.py, cli/skills.py, cli/_skill_bootstrap.py, firm/archive.py), so without --copy-metadata the smoke-test step's `--version` invocation would raise PackageNotFoundError on every matrix job, blocking manifest-and-release from publishing. Codex P1 (3rd pass on PR #63). Like the macos-13 fix, this is the exception case: workflow's whole purpose is producing release binaries, so a dead smoke test means zero release value. After this, the next signal worth acting on is workflow_dispatch on a fork — Codex's static analysis has hit its limit; only running the workflow exercises the full PyInstaller bundling chain.
…x context (PR #63) GitHub Actions evaluates a step's `shell:` field before matrix expansion, so `shell: ${{ matrix.shell }}` produces a workflow-syntax parse error ("Unrecognized named-value: 'matrix'") that blocks every trigger, including workflow_dispatch. Three earlier push-events failed silently for this reason. Removing the matrix.shell references and the now-unused `shell:` keys on matrix entries. The build steps that explicitly need pwsh (Windows backtick line continuations) keep their hardcoded `shell: pwsh`; the install/smoke-test steps fall back to GitHub's per-OS default (bash on Linux/macOS, pwsh on Windows), which is exactly what matrix.shell was specifying anyway.
Summary
.github/workflows/release-binaries.yml: a four-target PyInstaller matrix (linux-x64,macos-x64,macos-arm64,windows-x64) that runs onv*tags andworkflow_dispatch, builds a single-filewboxbinary per target, computes its SHA-256, and attaches the four binaries plus a combinedSHA256SUMS.txtmanifest to the GitHub Release for the tag.scripts/wbox_entry.py: a tiny PyInstaller entry shim that imports and callswealthbox_tools.cli.main:app. PyInstaller--onefileprefers a script file to amodule:functionreference, so the shim keeps the build invocation clean and avoids dynamic-import edge cases.publish(PyPI) job inci.ymlis untouched, satisfying the trusted-publisher constraint in CLAUDE.md. Both workflows fire onv*tags and operate independently.Closes #33.
Manifest contract for #42 / #43
Downstream installer rewrites (#42
install.sh, #43install.ps1) can rely on this contract being stable across releases:SHA256SUMS.txtsha256sum-compatible — one record per line,<hex-sha256><two-spaces><filename>wbox-linux-x64wbox-macos-arm64wbox-macos-x64wbox-windows-x64.exeSHA256SUMS.txtare uploaded to the same GitHub Release for the tag, so installers can fetch<release-url>/<binary>and<release-url>/SHA256SUMS.txtfrom the same prefix.The contract is also documented in a header comment block at the top of
release-binaries.yml.Design notes
ci.yml: chose a separate file. The 4-target matrix would clutterci.yml, the trigger filter (v*tags +workflow_dispatchonly — no PRs) is cleaner in isolation, the PyPIpublishjob stays trivially untouched, and this workflow can be disabled or re-run independently when debugging.macos-13for Intel x64 andmacos-14for Apple Silicon arm64. These are the conventional GitHub-hosted labels that produce the right native architecture without cross-compile.<binary>.sha256alongside its binary. Themanifest-and-releasejob concatenates these intoSHA256SUMS.txt, ASCII-sorts, and verifies withsha256sum -cbefore upload. The per-binary.sha256files are not uploaded as release assets —SHA256SUMS.txtis the single source of truth for installers.contents: read; only themanifest-and-releasejob is grantedcontents: write(needed forgh release upload).--clobberso re-runs don't fail.workflow_dispatchgating: the upload step is gated onif: startsWith(github.ref, 'refs/tags/'). On a manual dispatch (no tag), the build + checksum + manifest-verify still run end-to-end, so the user can validate the whole pipeline without cutting a real release. ASHA256SUMSartifact is also uploaded for inspection.--versionbefore checksumming, so a broken build fails fast.Test plan
workflow_dispatchon a fork; all four matrix jobs succeed and produce a binary + sidecar summanifest-and-releasejob concatenates the four sidecars intoSHA256SUMS.txtand thesha256sum -cverification passesv*tag push, the four binaries +SHA256SUMS.txtappear as assets on the GitHub Releaseci.ymlpublishjob still runs and publishes to PyPI on the same tag (unchanged behaviour)🤖 Generated with Claude Code