fix(ci): pin iOS Release to manual signing, stop minting dev certs#16
Merged
fix(ci): pin iOS Release to manual signing, stop minting dev certs#16
Conversation
…gUpdates The iOS archive step ran with -allowProvisioningUpdates against an Automatic-signing project, so each CI run on the ephemeral runner keychain (which only has the imported Apple Distribution cert) caused Xcode to mint a fresh Apple Development cert via the ASC API. After a handful of runs the team's cert quota filled and archives started failing with "Choose a certificate to revoke" + "No iOS App Development provisioning profiles matching 'lutra-labs.toss'" — both downstream of the same loop. Pin the iOS Release configurations of `toss` and `tossShare` to manual signing with the Apple Distribution identity and the AppStore profile names that the workflow already installs (matches the names in export-options/ios-export-options.plist). Debug stays Automatic so local development is unaffected. Settings are sdk-scoped on the multiplatform `toss` target so the macOS build path (CODE_SIGNING_ALLOWED=NO) is untouched. Drop -allowProvisioningUpdates and the -authenticationKey* flags from both archive and exportArchive — manual signing means xcodebuild resolves everything from the local keychain + installed profiles and never calls Apple. The ASC API key is still prepared for the altool upload step. The currently-orphaned Apple Development certs in the team's quota need to be revoked manually in the Apple Developer portal before the next CI run can succeed; this commit only stops the bleeding. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
9 tasks
pseudobun
added a commit
that referenced
this pull request
Apr 12, 2026
) ## Summary Two related production bugs surfacing as one user-visible problem: the CLI is unusable across machines. ### Bug 1: Cross-machine data divergence The Mac mini and MacBook show completely different sets of tosses despite both being signed in to the same iCloud account, both running the same release, and both pointing at the same \`iCloud.lutra-labs.toss\` container. \`release.yml:173-177\` strips both \`aps-environment\` and \`com.apple.developer.aps-environment\` from the macOS app's signed entitlements before codesigning, with a comment claiming *"we don't use APNs on macOS."* That's wrong on a load-bearing point: **\`NSPersistentCloudKitContainer\` (the engine SwiftData uses for \`cloudKitDatabase: .private(...)\` mode) needs \`aps-environment\` to receive CloudKit silent push notifications.** Without it the OS never wakes the app for "remote changes happened" pushes, so the local SwiftData store never *pulls* from CloudKit. Each Mac becomes a write-only island whose store reflects only what that machine itself wrote. I verified the parent app's embedded provisioning profile (\`/Applications/Tossinger.app/Contents/embedded.provisionprofile\`) authorizes \`aps-environment: production\`, so the entitlement *can* be present — the workflow just deletes it. The fix substitutes the value (development → production) instead of stripping the keys. ### Bug 2: CLI SIGABRTs at process exit \`toss ls\` consistently crashes after printing results with: \`\`\` NSInternalInconsistencyException 'Invalid parameter not satisfying: bundleIdentifier...' -[PKUserNotificationsRemoteNotificationServiceConnection initWithBundleIdentifier:] -[PKPushRegistry _registerForPushType:] -[NSCloudKitMirroringDelegate dealloc] -[NSPersistentStore dealloc] TossKit Container.deinit ListCommand.run \`\`\` Read bottom-up: the command finishes, prints results, exits → SwiftData tears down → \`NSPersistentCloudKitContainer\` deallocs → CloudKit cleanup tries to register for push types via \`PKPushRegistry\` → PushKit looks up the bundle identifier through some path that doesn't work for a sub-bundled \`.app\` helper invoked via \`/opt/homebrew/bin/toss → /Applications/Tossinger.app/Contents/Helpers/toss.app/Contents/MacOS/toss\` → \`bundleIdentifier\` resolves to nil → assertion fails → SIGABRT. The CLI doesn't actually need CloudKit. It opens the same SQLite store as the main app via the app group, performs a single read or write, and exits. CloudKit sync is the main app's job. ## Changes **\`.github/workflows/release.yml\`** (\`Prepare sanitized entitlements\` step) — replace the two \`Delete :*aps-environment*\` PlistBuddy lines with \`Set ... production\`. Updated the misleading comment to explain why APNs is actually load-bearing for CloudKit silent push. **\`Packages/TossKit/Sources/TossKit/Persistence/PersistenceStack.swift\`** — added a public \`CloudKitMode\` enum with two cases: - \`.automatic\` — wires the store to the private CloudKit database (current behavior) - \`.disabled\` — uses \`ModelConfiguration.CloudKitDatabase.none\`, preventing \`NSPersistentCloudKitContainer\` from being instantiated at all \`makeContainer\` now takes \`cloudKit: CloudKitMode = .automatic\`, so the app and share extension are bytewise-equivalent at runtime through the default. **\`tossinger/Support/TossEnvironment.swift\`** — passes \`cloudKit: .disabled\`. Doc comment explains the rationale (CLI doesn't await async push delivery anyway, and instantiating the CloudKit container in a sub-bundled helper SIGABRTs). Same store file, same data; only the transport layer for the CLI changes. ## Test plan - [ ] Build the CLI helper in Xcode after the TossKit + TossEnvironment edits — should compile cleanly - [ ] Run \`toss ls\` against the existing populated store — results print, process exits cleanly with no SIGABRT and no PushKit assertion in the logs - [ ] Run \`toss add "https://example.com"\` and \`toss delete <id>\` — same expectation, no crashes - [ ] Run \`toss ls\` 10× in a row on the macOS box where the crash was reproducible — expect zero crashes - [ ] Build and run the main macOS app — behaves identically to before (default \`.automatic\` mode → same \`.private(...)\` configuration) - [ ] Cut a release with this branch and inspect the workflow log for the \`Prepare sanitized entitlements\` step — the dumped plist (\`cat \"\${SANITIZED}\"\` already in the step) should now show \`aps-environment: production\` and \`com.apple.developer.aps-environment: production\` - [ ] Confirm \`codesign --verify --strict --verbose=2\` (already in the workflow at line 205) still passes — entitlements remain a subset of what the Developer ID profile authorizes - [ ] Install the new release on both Mac mini and MacBook, add a toss on one, wait ~30s, verify it appears on the other without manual refresh - [ ] Run \`toss ls\` on both machines after sync settles — they should converge ## Notes - The two open PRs (this one + #16 cert spam) touch different regions of \`release.yml\` (this one edits ~line 176, #16 edits ~lines 437/450) so they shouldn't conflict whichever order they merge in. - The share extension stays on the default \`.automatic\` mode — iOS extensions don't have the CLI's PushKit instantiation problem since they run in the parent app's bundle context. Could be revisited if it ever proves problematic. - Existing \`~/Library/Group Containers/group.lutra-labs.toss/default.store\` files (which carry CloudKit sync metadata from prior \`.private(...)\` use) open cleanly with \`.none\` — opening a CloudKit-mirrored store from a non-CloudKit context is a supported pattern and a no-op on the metadata side. The next time the app runs with full CloudKit it picks up CLI-written rows via NSPersistentHistoryTracker and pushes them. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
pseudobun
added a commit
that referenced
this pull request
Apr 12, 2026
🤖 I have created a release *beep* *boop* --- ## [1.3.1](v1.3.0...v1.3.1) (2026-04-12) ### Bug Fixes * **ci:** pin iOS Release to manual signing, stop minting dev certs ([#16](#16)) ([cd4df60](cd4df60)) * **macos:** restore CloudKit pull sync and stop CLI SIGABRT on exit ([#17](#17)) ([2e447ef](2e447ef)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please).
7 tasks
pseudobun
added a commit
that referenced
this pull request
Apr 12, 2026
…19) ## Summary v1.3.1 is dead on arrival: every launch SIGKILLs with \`Termination Reason: Namespace CODESIGNING, Code 1, Taskgated Invalid Signature\`. Diagnosed via side-by-side of the shipped binary's signed entitlements vs. its embedded provisioning profile: | Signed in v1.3.1 binary | Authorized by profile | |---|---| | \`aps-environment: production\` | **NOT PRESENT** | | \`com.apple.developer.aps-environment: production\` | ✅ present | The bare \`aps-environment\` key is the iOS convention. macOS uses only the \`com.apple.developer.*\` namespace for developer entitlements, and the Developer ID provisioning profile authorizes \`com.apple.developer.aps-environment\` but not the bare iOS-shaped key. amfi enforces "every restricted entitlement must be profile-backed" at load time, so the bare key — which #17 started adding alongside the correct one — causes amfi to SIGKILL the binary on exec. \`codesign --verify --strict\` in CI didn't catch this because it only validates structural integrity, not profile subset. That's amfi's job and it only runs at load time on the end-user's machine. ## Changes **\`.github/workflows/release.yml\`** — in the \`Prepare sanitized entitlements\` step, **delete** the bare \`aps-environment\` key on macOS releases instead of substituting it. Keep the \`Set :com.apple.developer.aps-environment production\` line — that's the macOS runtime key for CloudKit silent push delivery, and it IS in the profile's authorized list. ## Test plan - [ ] Merge this PR and let release-please open the v1.3.2 PR - [ ] Merge v1.3.2 release PR - [ ] Re-verify the \`Prepare sanitized entitlements\` log in the v1.3.2 release workflow — the dumped plist should show \`com.apple.developer.aps-environment: production\` and NO bare \`aps-environment\` key - [ ] After release ships: \`brew upgrade --cask tossinger\` on both Mac mini and MacBook - [ ] Launch the app — should no longer crash with SIGKILL - [ ] Add a toss on one machine, wait ~30s, verify it appears on the other (cross-machine CloudKit push sync should work since \`com.apple.developer.aps-environment\` is present) - [ ] Run \`toss ls\` — should exit cleanly (CLI opt-out from #17 still applies) ## Notes - This PR reverses **only the iOS-shaped half** of the APNs entitlement work from #17. The CLI CloudKit opt-out and the iOS cert-spam fix from #16 are untouched. - #17's \`toss/toss.entitlements\` source file still declares both keys with value \`development\` — that's fine for local Debug Xcode builds (Xcode automatic signing handles the iOS/macOS key split per-platform). The release workflow is the only place we need to strip the iOS shape for macOS distribution. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
iOS archive in `release.yml` ran with `-allowProvisioningUpdates` against an Automatic-signing project. Each CI run minted a fresh Apple Development cert via the ASC API on the ephemeral runner keychain (which only has the imported Apple Distribution cert) and the team's cert quota filled after a handful of runs:
```
error: Choose a certificate to revoke. Your account has reached the maximum number of certificates.
error: No profiles for 'lutra-labs.toss' were found: Xcode couldn't find any
iOS App Development provisioning profiles matching 'lutra-labs.toss'.
```
Both errors are downstream of the same loop. Switching iOS Release to manual signing kills it.
Changes
`toss.xcodeproj/project.pbxproj` — only the Release configurations of `toss` and `tossShare` are touched. Debug stays Automatic so local development on iOS sim/device is unaffected. Settings are sdk-scoped on the multiplatform `toss` target so the macOS build path (which uses `CODE_SIGNING_ALLOWED=NO`) is untouched.
`.github/workflows/release.yml` — drop `-allowProvisioningUpdates` and the `-authenticationKey*` flags from both `xcodebuild archive` and `xcodebuild -exportArchive`. With manual signing the cert is in the keychain and the profiles are on disk, so xcodebuild resolves everything locally and never calls Apple. The ASC API key file is still prepared for the `altool --upload-app` step that follows.
Manual step required before this can ship
The currently-orphaned Apple Development certs need to be revoked in the Apple Developer portal so the team has room to sign — this PR only stops the bleeding, it doesn't free the slots already burned. Path: portal → Certificates → revoke any `Apple Development` cert that isn't on a Mac you actively use. Don't touch `Apple Distribution` (that's the one CI imports).
Test plan
Note
This is independent of the upcoming v1.3.0 release that ships the force-update gate. Either order is fine, but if you ship 1.3.0 first you'll hit this same cert wall, so this should land first.
🤖 Generated with Claude Code