Skip to content

fix(homebrew): post_install cannot migrate legacy gateway.toml because Pathname#write refuses to overwrite #3746

Closed

Description

@sandeepstele

User Story

As a macOS user upgrading a Homebrew install of OpenShell, I want post_install to migrate my generated gateway.toml, so the upgrade completes and the gateway uses the current config.

Problem Statement

The formula's post_install migrates a legacy, formula-generated var/openshell/gateway.toml with gateway_config.write gateway_config_contents. Homebrew's Pathname#write refuses to replace an existing file. Library/Homebrew/extend/pathname/write_mkpath_extension.rb does this:

raise "Will not overwrite #{self}" if exist? && !offset && !mode&.match?(/^a\+?$/)

The migration branch only runs when the file already exists, so it always raises. post_install then aborts, and the old config stays in place. The steps after it also never run: the VM-driver entitlements write and codesign.

The call is in tasks/scripts/release.py on main, in the else branch that matches legacy_empty_gateway_config_contents / legacy_ipv6_gateway_config_contents. It also ships in the v0.1.1 openshell.rb release asset at line 132. The same formula already uses atomic_write for the entitlements plist, which overwrites safely.

Impact / Why This Matters

  • Any Homebrew user whose gateway.toml exactly matches one of the two legacy v1 configs hits this on upgrade. Those configs are exactly what earlier formulas generated, so this is the normal upgrade path, not an edge case.
  • brew upgrade or brew reinstall reports "The post-install step did not complete successfully".
  • The gateway keeps the legacy config. For the IPv6 variant that means bind_address = "[::1]:17670", which the migration exists to remove.
  • The driver-vm signing step is skipped.
  • Downstream installers that check brew's exit status fail. NVIDIA/NemoClaw's scripts/install-openshell.sh stops with status 69 ([macOS/WSL2][Onboard][Regression] OpenShell dashboard forward times out and blocks sandbox onboarding NemoClaw#11963 has the report).

Acceptance Criteria

  • Upgrading over either legacy v1 config rewrites var/openshell/gateway.toml to the current contents, and post_install completes.
  • A user-edited config (one that matches neither legacy config) is still left untouched.
  • python/openshell/release_formula_test.py asserts the overwrite-safe call. It currently asserts gateway_config.write gateway_config_contents (line 103), which locks the bug in.

Suggested fix: use gateway_config.atomic_write gateway_config_contents in the migration branch. The first-install branch can stay as write, because the file does not exist there.

Reproduction Steps

  1. On Apple Silicon macOS, install an OpenShell Homebrew formula that generated the legacy config. For example 0.0.106, which wrote:
    [openshell]
    version = 1
    
    [openshell.gateway]
    bind_address = "[::1]:17670"
  2. Upgrade to a formula that contains the migration. 0.0.116 reproduces it, and the v0.1.1 formula has the same code.
  3. Run brew postinstall --debug --verbose nvidia/openshell/openshell.

Expected: gateway.toml is migrated and post_install completes.
Actual: RuntimeError: Will not overwrite /opt/homebrew/var/openshell/gateway.toml, and post_install aborts.

Confirming the diagnosis: after I replaced the file by hand, brew postinstall completed and signed openshell-driver-vm with the hypervisor entitlement.

Environment

  • macOS 26.6, Apple M5 (arm64)
  • Homebrew 6.0.22
  • OpenShell upgraded from 0.0.106 to 0.0.116 through the nvidia/openshell tap (the pinned formula NemoClaw installs)
  • Code checked on NVIDIA/OpenShell main at a3ed8c7 and in the v0.1.1 openshell.rb release asset

Logs

==> Postinstalling openshell
==> /opt/homebrew/Cellar/openshell/0.0.116/bin/openshell-gateway generate-certs
Warning: The post-install step did not complete successfully
...
RuntimeError: Will not overwrite /opt/homebrew/var/openshell/gateway.toml

I'm not vouched yet, so I haven't opened a PR. The change is the one-line write → atomic_write swap above, plus the matching test assertion.

Activity

  1. sandeepstele commented on Sep 27, 2026

    @sandeepstele
    Author

    A note on scope: Homebrew only enables the overwrite guard inside install and post-install contexts. WriteMkpathExtension is prepended by Pathname.activate_extensions!, which postinstall.rb calls. A quick brew ruby check therefore overwrites happily unless it enables the extensions first:

    $ brew ruby -e 'Pathname.activate_extensions!; p = Pathname(f); p.write("version = 1\n"); p.write("version = 2\n")'
    RuntimeError: Will not overwrite …
    $ ... p.atomic_write("version = 2\n"); p.read   # => "version = 2\n"
    

    I tested the suggested change locally on main (a3ed8c7):

    • tasks/scripts/release.py: the migration branch now calls gateway_config.atomic_write gateway_config_contents. The first-install write stays.
    • python/openshell/release_formula_test.py: the test asserts the atomic_write migration call and that exactly one plain write remains.
    • test_generate_homebrew_formula_uses_channel_urls_and_exact_version passes with the change and fails against the current release.py.
  2. drew commented on Sep 27, 2026

    @drew
    Collaborator

    🏗️ build-plan

    Implementation Plan

    Issue type: fix
    Complexity: Low
    Confidence: High — the Homebrew overwrite guard and failing call are identified.

    Summary

    Use Homebrew's overwrite-safe atomic_write only when replacing an exact package-generated legacy gateway config. Keep first-install creation and user-edited config handling unchanged.

    Scope

    • tasks/scripts/release.py: replace the migration branch's write call with atomic_write.
    • python/openshell/release_formula_test.py: assert one first-install write and the migration atomic_write in generated output.
    • architecture/build.md: record the package migration invariant.

    Implementation Steps

    1. Change the migration write and verify the generated formula.
    2. Tighten the formula test and run it.
    3. Run repository checks and review the diff.

    Test Plan

    • Unit: run the Homebrew formula generation test.
    • Integration: Homebrew post-install reproduction requires macOS and Homebrew; validate the API against Homebrew's source and the generated formula.
    • E2E: N/A; no sandbox infrastructure behavior changes.

    Risks & Open Questions

    • Published release assets remain unchanged; a new formula must be published for affected upgrades.

    Documentation Impact

    • Update the existing build architecture overview. Published config docs already describe the intended exact-match migration behavior.

    Revision 1 — initial plan

  3. drew commented on Sep 27, 2026

    @drew
    Collaborator

    🏗️ build-from-issue-agent

    Implementation Complete

    PR: #3752

    The formula now uses atomic_write only when replacing an exact package-generated schema-v1 gateway config. First-install creation and user-edited configs are unchanged. The formula test checks both write paths.

    Tests

    • Focused formula test: passed.
    • mise run pre-commit: passed.
    • mise run test: passed, including 257 Python tests.
    • mise run ci: Go gateway tests failed because this host has /etc/openshell/gateways/default, which conflicts with their empty-system-directory assumption. This is unrelated to the formula change.

    Docs updated

    • architecture/build.md: documented the exact-match atomic migration rule.

    The issue will auto-close when the PR merges.

  4. added a commit that references this issue on Sep 27, 2026
    d1a19c7
  5. added this to the OpenShell 0.1.2 milestone on Sep 28, 2026
  6. removed
    state:triage-neededOpened without agent diagnostics and needs triage
    on Oct 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions