Skip to content

[BUG] Vendored TranscribeCpp.xcframework (macOS slice) has flattened symlinks — from-source builds fail codesign verification #693

Description

@Fe2-O3

Summary

The macOS slice of the vendored TranscribeCpp.xcframework (transcribe-cpp-swift 0.1.2) has a malformed framework bundle layout: the top-level entries are real files/directories instead of symlinks into Versions/A. Because the bundle then contains two divergent copies of the binary, code signing seals it invalid, and a from-source build of FluidVoice produces an app that fails codesign --verify.

This does not affect the official signed release (presumably repaired or signed differently in your pipeline), but it does mean contributors building from source get an app macOS can refuse to launch.

Affected

  • Dependency: altic-dev/transcribe-cpp-swift, version 0.1.2, revision a16df2905f9c715344374fd2d8dd97c2712a6d96
  • Declared in Fluid.xcodeproj/project.pbxproj (XCRemoteSwiftPackageReference "transcribe-cpp-swift"); used via import TranscribeCpp in Sources/Fluid/Services/WhisperProvider.swift
  • Introduced in a7876b1 ("feat(asr): migrate Whisper to TranscribeCpp", 2026-07-09)
  • Reproduced on v1.6.4 and v1.6.5; macOS 27.0, Apple Silicon (M3 Max), Xcode 27.0

What's wrong

In TranscribeCpp.xcframework/macos-arm64_x86_64/CTranscribe.framework, as downloaded by SwiftPM:

-rwxr-xr-x  CTranscribe     ← should be a symlink → Versions/Current/CTranscribe
drwxr-xr-x  Resources       ← should be a symlink → Versions/Current/Resources
drwxr-xr-x  Versions
drwxr-xr-x  Versions/Current ← should be a symlink → A

A macOS framework is a versioned bundle: the top-level CTranscribe and Resources must be symlinks into Versions/Current, and Versions/Current must be a symlink to Versions/A. Here they're all real, so Versions/A/CTranscribe and Versions/Current/CTranscribe are two separate 7.1 MB files with different SHA-256 hashes.

(The ios-* slices are flat, which is correct — iOS frameworks are flat bundles. Only the macOS slice is affected.)

Repro

git clone https://github.com/altic-dev/FluidVoice && cd FluidVoice
git checkout v1.6.5
xcodebuild build -project Fluid.xcodeproj -scheme Fluid -configuration Release \
  -derivedDataPath /tmp/dd \
  CODE_SIGN_IDENTITY="-" CODE_SIGN_STYLE=Manual DEVELOPMENT_TEAM="" \
  PROVISIONING_PROFILE_SPECIFIER="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=YES

codesign --verify --deep --strict /tmp/dd/Build/Products/Release/FluidVoice.app; echo "exit=$?"

Expected: exit=0
Actual: exit=1

FluidVoice.app: a sealed resource is missing or invalid
In subcomponent: .../Contents/Frameworks/CTranscribe.framework

Worth noting the build itself reports ** BUILD SUCCEEDED ** — the failure only shows up on verification, so it's easy to miss and ship an unlaunchable app.

Fix

Ideally in transcribe-cpp-swift: build/package the macOS slice with the standard versioned layout, e.g.

cd CTranscribe.framework
ln -s A Versions/Current
ln -s Versions/Current/CTranscribe CTranscribe
ln -s Versions/Current/Resources Resources

ditto and xcodebuild -create-xcframework preserve symlinks; a cp -R or a zip round-trip without symlink support will flatten them, which is the likely origin.

Workaround for anyone building from source — after building, before signing: remove the duplicated top-level CTranscribe, Resources and the real Versions/Current, recreate them as symlinks, then re-sign the framework and the app (frameworks first, app second, not --deep). That takes verification back to exit=0.

Happy to open a PR against transcribe-cpp-swift if that's the right place for it.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions