aterm bundles leaves a stale LaunchServices record, so regenerated bundles keep launching under Rosetta #1322

Open
opened 2026-08-27 08:30:45 +00:00 by coilyco-ops · 0 comments
Owner

Symptom

On kais-macbook-pro (Apple M3 Pro), every shell inside an aterm role window came up translated. sysctl.proc_translated returned 1 and uname -m returned x86_64, while hw.optional.arm64 was 1.

The visible failure was Homebrew. brew upgrade aos died with:

Error: coilyco-flight-deck/tap/aos: formula requires at least a URL

Homebrew resolved Hardware::CPU.arch = x86_64 from the translated shell, so the on_macos { on_arm { ... } } block in Formula/aos.rb never applied and the stable spec had no url. coilyco-flight-deck/tap/agent-compose failed identically. ward and specgen loaded fine because they carry darwin-amd64 specs. Under arch -arm64 the same formula loaded cleanly, which isolates it to the shell architecture rather than the tap.

Where the translation entered

Reading the P_TRANSLATED flag per pid down the live chain:

  • kitty in the role bundle process, translated = true
  • aterm, translated = false
  • claude, translated = false
  • /bin/zsh, universal, inherits the x86_64 spawn preference

aterm and claude are arm64-only Mach-O, so they are forced native and cannot themselves be translated, but the x86_64 binary preference passes through them to universal children like /bin/zsh.

The bundle on disk was already correct. Contents/Info.plist declared LSArchitecturePriority = (arm64) and the generated launcher contained no arch call. LaunchServices was holding a cached record from an older bundle generation and launching the /bin/sh wrapper x86_64 from it.

The gap

writeBundle in aterm/bundle.go:432 writes Contents/MacOS/<executable>, Contents/Info.plist, and Contents/PkgInfo, then returns. There is no lsregister call anywhere in aterm/bundle.go.

Rewriting those files in place does not refresh the LaunchServices record, so aterm bundles correctly emits an arm64 Info.plist and then leaves the stale record that overrides it. Regenerating looks like it fixed the problem and does not.

Repro

  1. Have a role bundle whose LaunchServices record predates the current LSArchitecturePriority emission
  2. Run aterm bundles
  3. Launch the role app and run uname -m in the window
  4. Observe x86_64 despite LSArchitecturePriority = (arm64) on disk

Suggested fix

Have writeBundle (or the caller that loops over the plan) re-register each bundle it writes on darwin:

lsregister -f -R <bundle path>

at /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister.

Best-effort is right here. A failed re-registration should not fail the generation, but it is worth surfacing in the non-json output so a stale record is visible rather than silent.

Verification of the manual workaround

After regenerating all 7 bundles and running lsregister -f -R on each, a disposable LSBackgroundOnly probe app launched through LaunchServices reported launcher_uname=arm64, launcher_translated=0, child zsh arm64, and brew info --formula coilyco-flight-deck/tap/aos succeeding from inside it.

The probe was a fresh bundle rather than one of the role records, so it confirms the launch path is healthy rather than proving each re-registered record specifically. The confirming step is the next real relaunch.

LaunchServices also still holds 3 dead records for me.coilysiren.aterm.archprobe.c with nothing on disk, left over from an earlier probe. Worth a sweep if the fix lands with a cleanup path.

Filed by Vera (sysadmin). aterm is shared tooling other seats build on, so the build hands over to Agentic Platform Engineer under boundary-build-foundational-software.

## Symptom On kais-macbook-pro (Apple M3 Pro), every shell inside an aterm role window came up translated. `sysctl.proc_translated` returned `1` and `uname -m` returned `x86_64`, while `hw.optional.arm64` was `1`. The visible failure was Homebrew. `brew upgrade aos` died with: ``` Error: coilyco-flight-deck/tap/aos: formula requires at least a URL ``` Homebrew resolved `Hardware::CPU.arch = x86_64` from the translated shell, so the `on_macos { on_arm { ... } }` block in `Formula/aos.rb` never applied and the stable spec had no url. `coilyco-flight-deck/tap/agent-compose` failed identically. `ward` and `specgen` loaded fine because they carry `darwin-amd64` specs. Under `arch -arm64` the same formula loaded cleanly, which isolates it to the shell architecture rather than the tap. ## Where the translation entered Reading the `P_TRANSLATED` flag per pid down the live chain: * `kitty` in the role bundle process, `translated = true` * `aterm`, `translated = false` * `claude`, `translated = false` * `/bin/zsh`, universal, inherits the x86_64 spawn preference `aterm` and `claude` are arm64-only Mach-O, so they are forced native and cannot themselves be translated, but the x86_64 binary preference passes through them to universal children like `/bin/zsh`. The bundle on disk was already correct. `Contents/Info.plist` declared `LSArchitecturePriority = (arm64)` and the generated launcher contained no `arch` call. LaunchServices was holding a cached record from an older bundle generation and launching the `/bin/sh` wrapper x86_64 from it. ## The gap `writeBundle` in `aterm/bundle.go:432` writes `Contents/MacOS/<executable>`, `Contents/Info.plist`, and `Contents/PkgInfo`, then returns. There is no `lsregister` call anywhere in `aterm/bundle.go`. Rewriting those files in place does not refresh the LaunchServices record, so `aterm bundles` correctly emits an arm64 Info.plist and then leaves the stale record that overrides it. Regenerating looks like it fixed the problem and does not. ## Repro 1. Have a role bundle whose LaunchServices record predates the current `LSArchitecturePriority` emission 2. Run `aterm bundles` 3. Launch the role app and run `uname -m` in the window 4. Observe `x86_64` despite `LSArchitecturePriority = (arm64)` on disk ## Suggested fix Have `writeBundle` (or the caller that loops over the plan) re-register each bundle it writes on darwin: ``` lsregister -f -R <bundle path> ``` at `/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister`. Best-effort is right here. A failed re-registration should not fail the generation, but it is worth surfacing in the non-json output so a stale record is visible rather than silent. ## Verification of the manual workaround After regenerating all 7 bundles and running `lsregister -f -R` on each, a disposable `LSBackgroundOnly` probe app launched through LaunchServices reported `launcher_uname=arm64`, `launcher_translated=0`, child zsh `arm64`, and `brew info --formula coilyco-flight-deck/tap/aos` succeeding from inside it. The probe was a fresh bundle rather than one of the role records, so it confirms the launch path is healthy rather than proving each re-registered record specifically. The confirming step is the next real relaunch. ## Related LaunchServices also still holds 3 dead records for `me.coilysiren.aterm.archprobe.c` with nothing on disk, left over from an earlier probe. Worth a sweep if the fix lands with a cleanup path. Filed by Vera (sysadmin). `aterm` is shared tooling other seats build on, so the build hands over to Agentic Platform Engineer under `boundary-build-foundational-software`.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-flight-deck/agentic-os#1322
No description provided.