Skip to content

Add a ci:macos-unsigned label for unsigned macOS arm64 test builds - #7

Open
Harold Hunt (huntharo) wants to merge 4 commits into
pwragentfrom
macos-arm-unsigned-build-a4113c
Open

Add a ci:macos-unsigned label for unsigned macOS arm64 test builds#7
Harold Hunt (huntharo) wants to merge 4 commits into
pwragentfrom
macos-arm-unsigned-build-a4113c

Conversation

@huntharo

Copy link
Copy Markdown

Getting a build onto an Apple Silicon Mac currently means either a workflow_dispatch run of the whole release pipeline, or applying ci:release-signing and pulling the unsigned signing-input-macos-aarch64 tarball out of a run that also entered both signing environments. Neither is what you want when the question is just "does this change work on my machine".

ci:macos-unsigned now runs one macos-15 job that builds aarch64-apple-darwin and attaches unsigned-macos-aarch64. The job summary carries the xattr -dr com.apple.quarantine line, since the binaries are neither signed nor notarized and Gatekeeper refuses them otherwise.

Why a separate workflow

Not a second label inside pwragent-release.yml. That pipeline is fail-closed and check-release-signing.py pins the property; adding an unsigned escape hatch would have meant gating the signing jobs off inside the one file written to make that impossible.

So the contract check is extended to cover the new workflow instead. It must not enter an environment, read secrets, take contents: write, or rename its artifact off the unsigned- prefix — this is an obvious place for a shipping path to grow later. pwragent-release-check.yml gains the file in its paths so the check actually runs when it changes.

Timeout and cache

Follows #6 rather than the pre-#6 shape: 240 minutes, and restore/save split with if: always() so a build killed at the cap keeps its partial target dir.

The cache key is deliberately the release workflow's cargo-macos-aarch64-<lock> — same target, same release profile, same three binaries — so the two legs read each other's codex-rs/target and a cold two-hour build only happens when neither has run recently.

Verification

  • check-release-signing.py passes. Each new assertion was mutation-tested: injecting secrets., an environment:, contents: write, and an artifact rename each fail the check, and the file restores byte-identical.
  • actionlint clean on the new workflow. (It flags macos-15-intel in pwragent-release.yml, but that is pre-existing — same warning at HEAD; actionlint's bundled runner list predates that label.)
  • The Stage distribution and Package unsigned artifact steps were extracted verbatim from the YAML and run against stub binaries: heredoc renders with no indentation leak, 0755 preserved, tarball layout matches the release one, signed=no present in PWRAGENT-BUILD.txt.

The macOS build itself is unverified — no local runner. The cargo build and rusty_v8 steps are copied from the release workflow's macos-aarch64 matrix leg, which builds today.

Trying it

Label this PR ci:macos-unsignedpull_request events use the workflow from the merge ref, so it applies to the PR that adds it. The workflow_dispatch button appears once this is on pwragent.

🤖 Generated with Claude Code

Harold Hunt (huntharo) and others added 4 commits August 18, 2026 22:24
)

Three fixes from the first end-to-end run.

Authenticode signing failed before signing anything:

  Cannot process argument transformation on parameter 'Files'.
  Cannot convert value to type System.String.

`Invoke-TrustedSigning -Files` is typed [string], not [string[]]. grok-build
passes a single path, which is the shape the module actually accepts; batching
five was my assumption and it was wrong. Now one call per binary, signing and
verifying in the same pass so a failure names the file it belongs to. Five
signing round-trips instead of one, which costs seconds.

Both macOS builds were killed at the 120 minute timeout. Measured on standard
hosted runners: linux-aarch64 43m, linux-x86_64 55m, windows-prepare 101m,
macos-aarch64 114m, macos-x86_64 killed at 120m. Standard hosted macOS is a
3-core M1 and roughly 2x slower than ubuntu for this workspace. Raised to 240,
well inside GitHub's 6 hour per-job ceiling.

The cache was also not being kept. The combined `actions/cache` skips its
post-step save when a job fails, so the timed-out macOS jobs discarded
everything they had compiled. Split into restore/save with `if: always()`, and
extended to cover codex-rs/target so a killed build resumes rather than
restarting. Upstream uses the same split in bazel.yml with a `!cancelled()`
guard; that guard is deliberately omitted here, since a timeout is precisely
the case whose output is worth keeping. The save key carries run_id and
run_attempt because cache entries are immutable once written.
Getting a build onto an Apple Silicon Mac meant either a workflow_dispatch
run of the whole release pipeline, or applying `ci:release-signing` and
pulling the unsigned `signing-input-macos-aarch64` tarball out of a run that
also entered both signing environments. Neither is what you want when the
question is just "does this change work on my machine".

`ci:macos-unsigned` now runs one macos-15 job that builds aarch64-apple-darwin
and attaches `unsigned-macos-aarch64`. The job summary carries the `xattr -dr
com.apple.quarantine` line, since the binaries are neither signed nor
notarized and Gatekeeper refuses them otherwise.

This is a separate workflow rather than a second label inside
pwragent-release.yml on purpose. That pipeline is fail-closed and
check-release-signing.py pins the property; adding an unsigned escape hatch
would have meant gating the signing jobs off inside the one file written to
make that impossible.

So the contract check is extended to cover the new workflow instead: it must
not enter an environment, read secrets, take `contents: write`, or rename its
artifact off the `unsigned-` prefix. Each assertion was mutation-tested.
pwragent-release-check.yml gains the file in its paths so the check actually
runs when it changes.

Timeout and cache follow #6 rather than the pre-#6 shape: 240 minutes, and
restore/save split with `if: always()` so a killed build keeps its partial
target dir. The cache key is deliberately the release workflow's
`cargo-macos-aarch64-<lock>` — same target, same release profile, same three
binaries — so a cold two-hour build only happens when neither has run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@huntharo Harold Hunt (huntharo) added the ci:macos-unsigned Build an unsigned macOS arm64 artifact for testing label Aug 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci:macos-unsigned Build an unsigned macOS arm64 artifact for testing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant