Skip to content

Cask-installed bundle hangs at _dyld_start when run from /Applications #25

Description

@drycode

The v0.1.1 cask installs correctly — signed, notarized, stapled, spctl accepts it — but the binary never executes a single line of its own code when run from /Applications. It blocks in the dynamic loader.

Reproduced on macOS 25.5.0 (Darwin), Apple Silicon, 2026-08-05.

Symptom

$ timeout 8 /Applications/Tacet.app/Contents/MacOS/tacet
$ echo $?
124        # timed out; expected 2, after printing "usage: tacet <serve|agent>"

main.swift cannot hang there — with no arguments it writes usage to stderr and exit(2) before touching AppKit. A sample of the hung process confirms it never gets that far:

Call graph:
    1597 Thread_163286913: Main Thread   DispatchQueue_<multiple>
      1597 _dyld_start  (in dyld) + 0  [0x10273c9c0]

The whole stack is _dyld_start. Same result for tacet serve under launchd: state = running, never binds its port, nothing in the log.

The bundle is fine — it is the path

The identical bundle, ditto'd elsewhere, runs instantly:

location result
/Applications/Tacet.app hangs, rc=124
/tmp/tt/Tacet.app (ditto of the above) usage: tacet <serve|agent>, rc=2
~/Applications/Tacet.app (same copy) usage: tacet <serve|agent>, rc=2

Same bytes, same xattrs (com.apple.macl, com.apple.provenance on both), same signature.

Ruled out, each by test

  • Signature / notarization. spctl -a -vvvaccepted / source=Notarized Developer ID. stapler validate passes. And the same bytes run elsewhere.
  • Quarantine. Homebrew applies com.apple.quarantine to casks by default. Removing it with xattr -dr did not change the hang.
  • Online Gatekeeper check. A copy runs fine, and the ticket is stapled, so no network is needed.
  • Contention from KeepAlive respawns. Hung instances pile up because launchd restarts them. Booting the service out entirely and killing every process still reproduced it.

Why this matters

tacet is never double-clicked — launchd starts it. A first-launch consent dialog, if that is what this is, has nobody to click it, so the app deadlocks instead of failing. Whatever the cause, an app that hangs in dyld gives an operator nothing: no log line, no exit code, no error.

It also makes the installers' verification step hang forever. install-server.sh runs the installed binary to prove it works:

usage_out="$("$APP_DST/Contents/MacOS/tacet" 2>&1 || true)"

With no timeout, that blocked for 10+ minutes before being killed by hand. That check needs a timeout regardless of what causes the hang — it is the difference between a diagnosable failure and a wedged install.

Not yet tried

  • Replacing /Applications/Tacet.app with a freshly written copy (new inode) to see whether the path or the specific file is at fault.
  • Whether this reproduces on a machine other than the author's. CI installs and uninstalls the cask but never executes it, so it provides no evidence either way.

Impact

The cask (drycodeworks/tap) should not be recommended until this is understood. Installing from source to ~/Applications is unaffected and works.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions