Skip to content

Fix jumpcall install for Homebrew installs (bundle not found + destructive --from fallback) - #3

Merged
joncode merged 1 commit into
mainfrom
fix/homebrew-install-path
Sep 1, 2026
Merged

Fix jumpcall install for Homebrew installs (bundle not found + destructive --from fallback)#3
joncode merged 1 commit into
mainfrom
fix/homebrew-install-path

Conversation

@joncode

@joncode joncode commented Sep 1, 2026

Copy link
Copy Markdown
Owner

The bug

Reported by a user on a MacBook Pro (M2, macOS Tahoe 26.5.1) following the README's recommended flow:

$ brew install joncode/tap/jumpcall
$ jumpcall install
jumpcall: could not locate a JumpCall.app bundle to install.
Run `make install` from the repo, or `jumpcall install --from <path to JumpCall.app>`.

The tap formula builds JumpCall.app into the Cellar (/opt/homebrew/Cellar/jumpcall/<v>/JumpCall.app) and symlinks /opt/homebrew/bin/jumpcall.../Contents/MacOS/jumpcall. But sourceBundle() only looked at Bundle.main.bundlePath, which for a symlinked invocation is /opt/homebrew/bin — not an .app — so the documented Homebrew path could never work.

The cascade

The error message then steered the user into something worse. They ran:

$ jumpcall install --from /opt/homebrew/bin/jumpcall
installed /Users/smokey/Applications/JumpCall.app
cli: /opt/homebrew/bin/jumpcall
could not run /Users/smokey/Applications/JumpCall.app/Contents/MacOS/jumpcall: The file "jumpcall" doesn't exist.
SMAppService registration failed — falling back to a LaunchAgent
launch agent installed: /Users/smokey/Library/LaunchAgents/io.github.joncode.jumpcall.plist
The file /Users/smokey/Applications/JumpCall.app does not exist.
JumpCall is running — look for the video icon in your menu bar.

Four distinct bugs in that transcript:

  1. --from was never validated. It accepted the CLI binary path and "installed" it as ~/Applications/JumpCall.app — a bare file, not a bundle.
  2. symlinkCLI() deleted the install source. It blindly removeItems whatever sits at /opt/homebrew/bin/jumpcall — here, the Homebrew-managed symlink the user had just passed as --from — and replaced it with a dangling symlink into the broken "bundle". After this, both the Homebrew install and the CLI are broken.
  3. A LaunchAgent was installed pointing at a nonexistent binary, and the launchctl bootstrap failure went to a discarded pipe.
  4. "JumpCall is running" printed unconditionally, right after open had just reported the app doesn't exist.

The fix

  • sourceBundle() resolves symlinks on the real executable and walks up to the enclosing .app (same trick AppInfo.version already used), requiring Contents/MacOS/jumpcall inside. brew install && jumpcall install now just works.
  • The same resolution applies to --from, so even the user's "wrong" command (--from /opt/homebrew/bin/jumpcall) now resolves through the symlink to the Cellar bundle and installs correctly. Genuinely invalid paths fail fast with a clear message instead of being copied.
  • symlinkCLI() only ever replaces symlinks — a regular file at the target path is left alone with a note.
  • launchctl bootstrap failures are reported; the final success line only prints when open exits 0, otherwise the command fails with a pointer to try Finder.

Covered by new installBundleTests() in the framework-free test runner (fake Cellar layout with the bin symlink, bare-binary rejection, bundle-without-binary rejection). make test: 79 passed, 0 failed.

Possible follow-ups (not in this PR)

  • The formula could bin.write_exec_script or the caveats could clarify that the app lives in the Cellar until jumpcall install copies it out.
  • Replacing the Homebrew-managed symlink at /opt/homebrew/bin/jumpcall with our own (current behavior when it is a symlink) survives until the next brew upgrade relinks it — consider leaving brew's link alone and preferring ~/.local/bin when the existing symlink isn't ours.
  • A --dry-run flag, and printing each step's target before acting, would make transcripts like the one above self-diagnosing.
  • The friend's last confusion — "I see no video icon" — was the menu-bar overflow case jumpcall status already explains; install could end by pointing at jumpcall status.

🤖 Generated with Claude Code

…ete files we don't own

`brew install joncode/tap/jumpcall && jumpcall install` — the README's
recommended flow — failed with "could not locate a JumpCall.app bundle".
The formula builds JumpCall.app into the Cellar and symlinks
/opt/homebrew/bin/jumpcall into it, but sourceBundle() only checked
Bundle.main.bundlePath, which reports the symlink's directory.

Worse, the error suggested `--from <path>`, and --from accepted any path
unvalidated. Passing the CLI binary itself copied a bare file to
~/Applications/JumpCall.app, then symlinkCLI() deleted the Homebrew
symlink at /opt/homebrew/bin/jumpcall (the install source!) and replaced
it with a dangling link — and the command still printed "JumpCall is
running".

- resolve symlinks on the executable (and on --from arguments) and walk
  up to the enclosing .app, requiring Contents/MacOS/jumpcall inside
- reject invalid --from paths with a clear error instead of copying them
- symlinkCLI only ever replaces symlinks, never regular files
- report launchctl bootstrap failures instead of piping them to nowhere
- only print the success line when `open` actually succeeded

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@joncode
joncode merged commit db549bf into main Sep 1, 2026
1 check passed
@joncode
joncode deleted the fix/homebrew-install-path branch September 1, 2026 05:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant