From 389add428b41cc4f301b1d1f6654bdebe9f235f2 Mon Sep 17 00:00:00 2001 From: roboomp Date: Fri, 12 Jun 2026 14:59:45 +0000 Subject: [PATCH] fix(brew): unbreak the generated homebrew formula install Render Formula/omp.rb with two changes so the formula actually installs: - Stamp `using: :nounzip` on every per-platform `url` stanza so Homebrew leaves the downloaded bare Mach-O/ELF asset in the staging CWD; the default `CurlDownloadStrategy` was routing through `UnpackStrategy::Uncompressed#extract_nestedly`, nesting the file outside CWD so `Dir["omp-*"].first` returned `nil` and `bin.install nil => "omp"` raised. - Wrap `generate_completions_from_executable` in `with_env(HOME: buildpath)` so the popened binary's `os.homedir()` resolves into the writable staging dir instead of the real `/Users//.omp`, which Homebrew's sandbox profile denies read access to. Also export `renderFormula` (gated by `import.meta.main`) and add a unit test that pins the rendered stanza shape for future regressions. Fixes #2398 --- packages/coding-agent/CHANGELOG.md | 1 + scripts/ci-update-brew-formula.test.ts | 50 ++++++++++++++++++++++++++ scripts/ci-update-brew-formula.ts | 32 +++++++++++++---- 3 files changed, 76 insertions(+), 7 deletions(-) create mode 100644 scripts/ci-update-brew-formula.test.ts diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 147feaa9a..3ce7bf137 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -5,6 +5,7 @@ ### Fixed - Collab links now dot-join the room secret (`.`, `host/r/.`) instead of using a second `#`: RFC 3986 forbids a raw `#` inside a URL fragment, so macOS Foundation (behind terminal click-to-open) percent-encoded the browser deep link's second `#` to `%23` and the web client rejected the session. `/join`, `omp join`, and the web client still accept legacy `#`-joined links and leniently decode `%23`-mangled ones +- Fixed `brew install can1357/tap/omp` failing on macOS with `sandbox-exec … exited with 1`: the generated `Formula/omp.rb` (rendered by `scripts/ci-update-brew-formula.ts`) now stamps `using: :nounzip` on every per-platform `url` so Homebrew leaves the bare Mach-O/ELF asset in the staging CWD (the previous `Dir["omp-*"].first` returned `nil` because `UnpackStrategy::Uncompressed#extract_nestedly` nested the file outside it), and wraps `generate_completions_from_executable` in `with_env(HOME: buildpath)` so the popened binary's `~/.omp` lookup goes to the writable staging dir instead of the sandbox-denied real home ([#2398](https://github.com/can1357/oh-my-pi/issues/2398)) ## [15.12.1] - 2026-06-12 diff --git a/scripts/ci-update-brew-formula.test.ts b/scripts/ci-update-brew-formula.test.ts new file mode 100644 index 000000000..84234fc1b --- /dev/null +++ b/scripts/ci-update-brew-formula.test.ts @@ -0,0 +1,50 @@ +import { describe, expect, it } from "bun:test"; +import { renderFormula } from "./ci-update-brew-formula"; + +const SUMS = { + "omp-darwin-arm64": "darwin_arm64_sha", + "omp-darwin-x64": "darwin_x64_sha", + "omp-linux-arm64": "linux_arm64_sha", + "omp-linux-x64": "linux_x64_sha", +}; + +describe("renderFormula", () => { + const formula = renderFormula("15.12.1", SUMS); + + // Regression: bare-binary URLs must opt out of Homebrew's UnpackStrategy. + // Without `using: :nounzip` the default CurlDownloadStrategy nests the file + // outside the staging CWD, `Dir["omp-*"].first` returns `nil`, and + // `bin.install nil => "omp"` raises (issue #2398). + it("attaches `using: :nounzip` to every per-platform url stanza", () => { + const matches = formula.match(/using: :nounzip/g) ?? []; + expect(matches).toHaveLength(4); + for (const arch of ["omp-darwin-arm64", "omp-darwin-x64", "omp-linux-arm64", "omp-linux-x64"]) { + expect(formula).toMatch( + new RegExp( + `url "https://github\\.com/[^"]+/${arch}",\\s+using: :nounzip\\s+sha256 "${SUMS[arch as keyof typeof SUMS]}"`, + ), + ); + } + }); + + // Regression: completions generation must run with HOME redirected so the + // popened binary doesn't touch the real `~/.omp` (denied by Homebrew's + // sandbox profile) during the build (issue #2398). + it("wraps `generate_completions_from_executable` with a HOME redirect to buildpath", () => { + expect(formula).toMatch( + /with_env\(HOME: buildpath\) do\n\s+generate_completions_from_executable\(bin\/"omp", "completions", shells: \[:bash, :zsh, :fish\]\)\n\s+end/, + ); + // And the bare form (which is what failed in the sandbox) must not appear + // outside the `with_env` block. + const blockless = formula.replace(/with_env\(HOME: buildpath\) do[\s\S]*?end/, ""); + expect(blockless).not.toMatch(/generate_completions_from_executable/); + }); + + it("emits the expected per-asset sha256 next to each url", () => { + for (const name in SUMS) { + const sha = SUMS[name as keyof typeof SUMS]; + expect(formula).toContain(`/${name}",`); + expect(formula).toContain(`sha256 "${sha}"`); + } + }); +}); diff --git a/scripts/ci-update-brew-formula.ts b/scripts/ci-update-brew-formula.ts index 36a2efad5..96a72b7e7 100755 --- a/scripts/ci-update-brew-formula.ts +++ b/scripts/ci-update-brew-formula.ts @@ -54,7 +54,17 @@ function sha256For(assets: readonly ReleaseAsset[], name: string): string { // `${...}` is JS interpolation; the literal `#{version}` / `#{bin}` below are // Ruby interpolations Homebrew resolves when it evaluates the formula. -function renderFormula(version: string, sums: Record): string { +export function renderFormula(version: string, sums: Record): string { + // Each `url` carries `using: :nounzip` because the release assets are bare + // Mach-O/ELF executables, not archives. Without it Homebrew's default + // CurlDownloadStrategy routes through UnpackStrategy::Uncompressed#extract_nestedly, + // which nests the file outside the staging CWD; `Dir["omp-*"].first` then + // returns `nil` and `bin.install nil => "omp"` raises. + // + // `with_env(HOME: buildpath)` redirects the CLI's `os.homedir()` lookup to + // the writable staging dir so `generate_completions_from_executable` does + // not touch the real `/Users//.omp` (denied by Homebrew's sandbox + // profile, which would otherwise fail the popen). return `class Omp < Formula desc "${DESC}" homepage "${HOMEPAGE}" @@ -63,22 +73,26 @@ function renderFormula(version: string, sums: Record): string { on_macos do on_arm do - url "https://github.com/${REPO}/releases/download/v#{version}/omp-darwin-arm64" + url "https://github.com/${REPO}/releases/download/v#{version}/omp-darwin-arm64", + using: :nounzip sha256 "${sums["omp-darwin-arm64"]}" end on_intel do - url "https://github.com/${REPO}/releases/download/v#{version}/omp-darwin-x64" + url "https://github.com/${REPO}/releases/download/v#{version}/omp-darwin-x64", + using: :nounzip sha256 "${sums["omp-darwin-x64"]}" end end on_linux do on_arm do - url "https://github.com/${REPO}/releases/download/v#{version}/omp-linux-arm64" + url "https://github.com/${REPO}/releases/download/v#{version}/omp-linux-arm64", + using: :nounzip sha256 "${sums["omp-linux-arm64"]}" end on_intel do - url "https://github.com/${REPO}/releases/download/v#{version}/omp-linux-x64" + url "https://github.com/${REPO}/releases/download/v#{version}/omp-linux-x64", + using: :nounzip sha256 "${sums["omp-linux-x64"]}" end end @@ -86,7 +100,9 @@ function renderFormula(version: string, sums: Record): string { def install bin.install Dir["omp-*"].first => "omp" (bin/"omp").chmod 0555 - generate_completions_from_executable(bin/"omp", "completions", shells: [:bash, :zsh, :fish]) + with_env(HOME: buildpath) do + generate_completions_from_executable(bin/"omp", "completions", shells: [:bash, :zsh, :fish]) + end end test do @@ -114,4 +130,6 @@ async function main(): Promise { } } -await main(); +if (import.meta.main) { + await main(); +}