Building and shipping Mac and iOS apps without opening Xcode
scottwillsey.com
scottwillsey.com
The fact that xAI uploaded someone's home directory, including their SSH keys, is giving me serious pause at my choices here.
Generally, I don't worry about my machine being "blown up". I don't have a TON of unreproducible stuff on my machine. Everything is backed up, committed to git, and the like. I can restore most of it in a couple hours.
That said, I really, really don't want my .SSH directory sent to an AI agent and it's silly to prompt your way around that. You need to block it at the system level. I'm considering a separate user and then 700 permissions on my home directory.
I feel like we're back to 1990's security here. The double-edged sword is that it's helping us get things done at a pace like never before.
I'm not throwing shade here, I'm among the guilty.
So, the actions that I really don't want the agent to take (establishing an SSH connection, pushing to a git remote) always require my manual intervention.
[1] https://gist.github.com/arianvp/5f59f1783e3eaf1a2d4cd8e952bb...
I'm treating it like...you can hold me accountable for that commit and the commit came from my computer. That feels like the right spirit of things.
How do you do commit signing? I've never worked anywhere it was common, nor worked on a team where anyone else signed commits. I don't know how it's really used in the wild, so to speak.
Commit signing primarily exists because linux kernel development happens via email, which isn't a secure channel.
In that context, signing is used to prove provenance; a commit signed by someone with merge rights gets included, and if you repeatedly sign bad code you'll have your right to merge revoked.
> you can hold me accountable for that commit
I wouldn't personally want to be held accountable for a commit I'd never so much as read, so I would never sign (even locally) a commit until I'd looked at it.
> I wouldn't personally want to be held accountable for a commit I'd never so much as read, so I would never sign (even locally) a commit until I'd looked at it.
I think that's reasonable. I'd say that if I push the commit, then I should be accountable for it whether I read it or not (and no one is going to see it unless I push it).
In terms of risk, I see it as halfway between stock claude with the sandbox and full-blown container or machine isolation.
I was recently thinking that as Claude's own sandbox gets better I'm doubting the ROI on my belt-and-suspenders project, but your comment reminds me why I'm doing it.
It is not currently published open source but I'm happy to talk about it with strangers.
You can also use native Mac VMs.
Many people have created Mac VM projects to do exactly this, I was working on one but was stalled too often because before I started using claude I bought a new laptop with what I KNEW was enough disk space.
The 100 GB or so I need to comfortably do the VM stuff just isn't available on my mac.
You don't have to! All recent Macs come with nearly zero perf cost virtualization. You can easily run Mac or Linux VMs assuming same architecture. Use it all the time for development and whatever.
Use Tart [1] or VirtualBuddy [2], both open-source, for a packaged solution. Or in the spirit of this post, vibe code your own wrapper around the OS API [3]
More recently, there are also Apple containers [4]
[2] https://github.com/insidegui/VirtualBuddy
[3] https://developer.apple.com/documentation/virtualization
> vibe code your own wrapper around the OS API
Maybe stick with one of the existing projects? Throwing out all of the edge cases and hardening that went into a security-related project just to burn a bunch of resources to generate a worse one you have to maintain yourself is 90s-level security with 2026-level inefficiency, IMO.
But it is to note that the OS APIs do the heavy lifting, the projects are just wrappers
On "Hacker" News of all places :)
vs
> vibe code your own wrapper around the OS API
https://developer.apple.com/documentation/virtualization/run...
If the below sounds too complex to work with, can review more basics first. Can start with a Swift tutorial
virtualMachine.start(completionHandler: { (result) in
if case let .failure(error) = result {
fatalError("Virtual machine failed to start with \(error)")
}
})The problem is that nowadays, people roll their own solution, don’t take the time to learn (somewhat understandably, given the complexity of today’s stuff. You simply cannot learn all details about your hardware, OS, and third-party libraries anymore) but immediately deploy things in production.
Eating your own dog food can be good, but for security related stuff, it can easily be disastrous.
Not to decent mistakes or amateur brain surgery, but there has to be some level of risk tolerance among tinkerers.
It’s also a hell of an indictment of this community. FreeBSD is a community project. Qubes is a community project. SELinux is a community project. And people wonder why developers don’t take vibe coders seriously.
I love how people say this like Apple finally created a native solution...
They didn't. It's just a linux vm running normal docker engine.
OCI compatible, but not docker
https://github.com/apple/container/blob/main/docs/technical-...
Actually makes use of Virtualization.framework mentioned above.
Linux VMs are not relevant here. For downloading Xcode and building apps, I believe one needs to sign in with their Apple account.
Does this work well with a macOS VM?
See docs here: https://developer.apple.com/documentation/virtualization/vir...
And projects like Tart, VirtualBuddy, etc. provide convenient wrappers for these APIs.
Or better yet it needs dead-end privileges with a system and data that doesn't matter.
Akamai akr should be able to do something similar, but would store the private key on your phone. It's the successor of krypton, which was bought by Akamai, and I liked that even more as you really need a second device.
You can have something like Claude's auto approvals. You can poke specific holes in the sandbox (E.G. my Codex one can write to ~/go, ~/.cache and ~/.cargo). You can have explicit deny rules that hard-deny and bypass the sandbox and auto-approval. You can allow certain commands to bypass sandbox execution entirely.
While those aren't secure in theory (if you let a third party type prompts into the agent, they will likely be able to exfiltrate your data), they're secure against agent carelessness and AI stupidity in practice.
re: this article: excellent! I always do about 90% of my Swift dev on the command line and I learned new tricks from the article. Bookmarked.
Surprisingly, it's very easy. This works like a charm: https://github.com/xtool-org/xtool
You do not need to upload to TestFlight or the App Store; you can just install the app locally to your iPhone via usb -- even from Linux!
When in doubt, just ask your coding agent of choice to help you create and upload a Hello World iOS app. It's really easy.
This is the perfect use case for a skill: one person takes the hit to create the skill and then anyone else who wants the tool can use the skill.
So you can develop ios apps, run it on a real iphone that is activated without going through app store as a native app? But, you have to reinstall it every week?
In addition to a deep roster of skills and agencts, Axiom includes several for-LLM tools². xclog, xcprof, xcsym, and xcui are designed to be used by LLMs, and expose capabilities in a token-efficient way. These tools are equally helpful for non-Axiom skills/agents.
¹ Axiom: https://charleswiltgen.github.io/Axiom/ ² Axiom CLI tools: https://charleswiltgen.github.io/Axiom/tools/
> I had Claude Code create mine: I told Claude, more or less: I want to archive, Developer ID-sign, notarize, staple, and install this app to /Applications without ever opening Xcode. Write me a script that does the whole chain and fails loudly if any step breaks.
Even though the text we're reading is Claude talking to us as well :)
Also it was weird to see the mention of "ask your LLM" at almost every stage in the blog post:
> point Claude Code or your LLM coding tool of choice to this blog post, and let it figure it out
> When in doubt, ask your LLM of choice about them and have it help you get set up. It’s the one that’s going to be using Xcode for you anyway.
> The whole point of using the LLM in the first place is to avoid doing things manually that you don’t want to do.
> Again, if in doubt, ask Claude Code or your LLM of choice to create this for you.
> Again, this is why you talk to your LLM, tell it what you want, and have it help build your workflow.
If I had to guess, I would say this is the human summarised conversation(s) with a bot.
I am getting a little tired of every single HN comment being about how the linked article is written by an LLM.
I'm not getting that vibe from this one; I'd bet money on it at least being substantially human edited. What are the tells you're seeing?
Xcode is a [buggy as hell] GUI wrapper for a lot of system-level UNIX utilities and apps [which are generally, not so buggy].
Using CLI to release apps is a pretty old practice; at least as long as I've been doing it (I released my first Xcode app in 2012).
Hope you like long argument lists. xcodebuild has a crazy long parameter list.
https://developer.apple.com/documentation/xcode/xcode-comman...
LLMs will often use CLI tools. In my experience, they loves their CLI.
https://developer.apple.com/tutorials/data/documentation/xco...
2011: https://manpagez.com/man/1/xcodebuild/
2016: https://keith.github.io/xcode-man-pages/xcodebuild.1.html
Source code for xcodebuild not public
?
Most of the problems that I saw people have with Xcode over the two decades that I worked on it were the result of one fundamental problem: They wanted to work a different way than Xcode is designed to work, and jumped through a ton of hoops to do so, and then blamed Xcode for not accommodating their intentional working at cross-purposes.
A good example: An app that considered itself “large” because it had a thousand tiny static library targets each with only one to three or so ObjC classes. Far smaller than Xcode itself, but because they insisted on working that way—for no real articulable benefit besides “that’s how we want to work”—their project took hours to build from scratch instead of minutes like much larger projects that have many fewer targets.
In my experience, it’s quite “crashy.” It often locks up so bad, that I need to force-quit (or force-restart the Mac). I end up doing that once a day, or so. I suspect that many of the performance and stability issues, have to do with resource usage. It’s a damn big app.
It can also end up in strange states, where portions of the UI fall “out of sync,” and the app needs to be restarted. Some operations (like opening a storyboard, or stopping at a breakpoint) introduce massive delays.
It’s not a denigration, calling something a UI shell. I’m working on one, right now. The engine and backend were done in April (started in February), and all the work since, was on the UI shell. I probably could have shipped something that “ticked all the boxes,” in May. It’s almost done, but there’s still a couple of months of testing, ahead.
I should blog about more this, but I also went to some effort to add support free iOS provisioning with just an Apple ID (using internal APIs); and creating a nice DMGs for macOS app distribution (reverse engineered .DS_store files for this). And there's also a built-in command to install skills for coding agents, which was fun.
This [1] is an example app I built with it, a simple utility to manage macOS file extension handling.
Its basis is React, so the code output quality is much higher than Swift because there is much more React code in LLM training data.
Everything is in the command line, and debugging is a breeze because it's a web view. But once it's compiled to native iOS, it feels like any other native app.
Expo + Fastlane = fully automated iOS submission and deployment. I issue one command and see a new version in the App Store.
Our code broke
It looks like there was an error we need to look at. Sorry about the inconvenience.fwiw, expo seems solid; the uptime of expo.dev won't affect an app built with it.
As for the builds, your agent probably already knows how to do a lot of this from the command line, although explicit suggestions can help it build faster for different situations.
As for XcodeGen, you may find it unnecessary overhead if you're already using Xcode file system synced groups.
For iOS my biggest suggestion would be to enable App Store Connect skills for your agent (https://github.com/rorkai/App-Store-Connect-CLI).
With this not only do you not need Xcode all the time, you also don't even need to be near a MacBook.
Just make changes via Codex on your iPhone, the tell asc to build and upload to TestFlight, download and run the new version, iterate.
I only used Xcode to preview the builds on my device and then guide the prompts more
Having spent many years fighting with Xcode professionally, I was thrilled when swift build came out, and then appalled at how badly Xcode adopted it. Eventually I realized that you can set up your entire app as a swift package, and then the Xcode project with a single main source file that calls some `app_main()` (or whatever you want to call it) function in your package.
I cannot remember exactly but there was something annoying about setting up a new project; I think by default Xcode assumes that you want a swift package to be treated as a dependency that is checked out from git, but there was a way to drag and drop a local dependency in the same project directory and then it worked.
What this buys you is that files are no longer tracked by Xcode, so renames/merges no longer trigger project.pbxproj surgery. Instead you specify the swift package directories and are done.
I used this for some large personal projects and was very happy with it.
At one point I also wrote my own build tool that imitated all of the steps that Xcode took so that I could truly be Xcode-free. That also worked (and was not even that hard) but I went back to Xcode because the debugger UI was better than what I could get out of VS Code at the time, and especially because I was playing around with the Metal debugger.
> set up your entire app as a swift package, and then the Xcode project with a single main source file that calls some `app_main()`
this is basically how i do all apps nowadays (at home and at $work), all settings for the ios side of things is in a xcconfig file and keeps things simple and working with multiple people is much more a breeze this wayXcode MCP is not perfect, by any stretch (stupidly, it issues a permission prompt on every single agent launch—something I defeated with a Keyboard Maestro hack to auto-accept it), but Xcode 27 is such a huge leap forward in the ergonomics of vibe coding with Xcode that I've actually stopped dealing with the MCP and (after an afternoon of futzing) have started using the app's own agent UI for driving Codex because that harness better steers the model to take advantage of the tools available.
All of this redounds to two things: faster feedback and more robust verification. The two things that matter most.
Forcing people onto Macs is such a pathetic way of extracting rent from developers and makes workflows worse. No you can't just build and debug your app from your Windows or Linux workstation because we want you to buy hardware you don't need. Sorry we only support Xcode on the latest version of MacOS, how else will your build machine roll out of the OS support window and make you replace a functioning machine?
It's especially nice when you work on projects that aren't iOS/Apple native and you don't get Mac workstations. You end up with this wonderful mess of shared machines and half assed tools because you just need the bare minimum to push a build to a phone.
> It's especially nice when you work on projects that aren't iOS/Apple native and you don't get Mac workstations.
If you develop natively for macOS, then you probably need the OS for it. It's the same for Linux and Windows. But building is not the biggest hurdle because you can build in your usage-based-billed/free CI as well the same way you do for other platforms.
For complete native development (system APIs, OS interactions, native UI) against macOS/iOS on PC, Apple needs to open those OSes for PCs/Pixels/Samsungs/etc. This is a software issue, not hardware. Otherwise Apple silicon Macs are less restrictive than PC hardware in general!
I have nothing against the hardware. I despise how artificial the problem is. It's not even like Apple just refuse to provide tools and leave it up to the community. They force you to use a Mac in the terms of service.
For context I work on the mobile team for a large AAA game engine. This is going to colour my opinion because we're very Windows centric, but my experience wouldn't exist if it weren't for Apple. I need to sync code on two different machines and build half the project on Windows and the other half on the Mac. If we could just build and debug the iPhone on Windows none of this mess would be needed.
We don't develop for MacOS, we develop for iOS. Just debugging the app requires several hoops across multiple machines because of Apple's policy here. Just because a base spec Mac Mini is cheap doesn't make this whole problem any less stupid. I don't need a vendor's special-sauce computer to build for consoles. Cross compilation and remote debugging has existed for decades, this problem should not exist.
Calling it more affordable because Mac Minis aren't junk anymore is really just accepting Apple's ridiculous requirement because "I guess it could be worse right?".
This is ironic. Of course you do. You're already using it. You just happen to like the Microsoft's walled garden and not Apples. That's fine, but let's not pretend that "Windows centric" is anything but another monopolistic and exclusionary ecosystem. If you want to use Nvidia drivers, or develop against DirectX, can you do it on a macOS? I get the criticism of Apple's walled garden and I'm against it too, but not from your position.
If you can produce a properly structured submission to the App Store ingestion pipeline without using a Mac, then I doubt Apple will care. But that doesn’t mean they’re obligated to help you, and especially not to spend millions of dollars to do so.
Apple could've chosen to offer no tools and leave it to the community to build their own. I think that's reasonable. I don't expect Microsoft to bring MSVC to Linux.
But Apple take the explicit position that you must purchase a Mac in order to ship apps for iOS.
You can develop for Linux and BSD and Haiku and all sorts of other platforms for free. If the cost is what matters to you, then just do that. If you’re doing it as a business, though, consider that you’re extremely lucky to be able to just buy a low-end device and a low-end system and that’s all the capital investment needed (plus the US$100/year membership as recurring opex).
Using godot iOS export.
For regular old native ios dev replacing Xcode i don’t think i would do it.
Interestingly, since about Opus 4.6, Claude has been able to reason its way into this process on its own. It was clunky until 4.7, and in 4.8 it's managed to find its way around every reason I had to open xcode myself.
Claude was great at figuring out what was broken when and either fixing it, or clicking as far as it could until it needed me.
You could say I'm mostly just IRL hands for the AI now.
At first, I just used simulator builds to test (and debug) on the macOS VM, in the simulator, but that was slow and painful. Finally, I bought a used iPhone, all bruised, just so I could test it on a real device. The main issue for me was signing, one cannot register a macOS VM (maybe with some kext hacks?), I managed to enable developer mode on the phone (crazy process, but it worked with Xcode in a VM), I registered the phone on the website, and paid those damn 99$.
I used that for my cross-platform UI library with native controls, based on IUP. Now, future users of the UI toolkit do not need to care about Mac, iOS, or tooling; it just works. You can check the toolkit (WIP) here https://github.com/gen2brain/iup-go .
To anyone who's chased a crash that only reproduces on a physical device knows the fix was never more log statements added by ai but rather a breakpoint, the debugger, and thirty unglamorous minutes of stepping through code line-by-line (terminal lldb technically counts, but barely).
I did end up somehow installing Xcode via some shady download and was on my way. But the whole ordeal left a very sour taste.
My only familiarity with it is because it's needed by brew. I honestly never looked into exactly what is in the package, but I assumed things like what is installed with -devel packages of yum/apt-get/dnf/etc. Lots of repos have common list of things to install like gcc/make/etc. Again, just guessing, but it's one of the first things I've always run on a new Mac to get it usable for CLI usage.
I’m not defending Xcode (I hate it), just clarifying.
Ignite is a great starter using RN and Expo. Some great people maintaining it. https://github.com/infinitered/ignite
npx expo run:ios --device #runs a build on my iphone and shows logs in the terminal
eas build --platform ios --profile preview #builds in the cloud and gives me a QR code to install a build on my device with a custom certificate
eas build --platform ios --profile production --auto-submit #auto increments and submits a production build to the app store.
“From bundle install to your phone in minutes. To the App Store and Google Play without a line of native code.“
https://github.com/software-mansion/argent
or
https://github.com/callstack/agent-device
both callstack and swmansion are mostly react native shops but those should work even in native ios/android as well
Kind of fun, you can develop iOS and Android both without a build step and without a Mac even.
React Native and Flutter seem to be much more predictable for the bots (and more fun for humans, since they have actual hot reload).
That said, it might be well enough for simpler apps.
* A Markdown viewer (obviously pretty simple)
* A menu bar Apple TV remote (surprisingly complicated)
* A menu bar and desktop temperature tracker for the silly Tapo temperature trackers I have all over my house (a little complicated because multiple user interfaces, a backing database, scheduling, and multiple local/remote protocol interfaces)
* A graphical CAS calculator frontend to Sage Math with LaTeX math rendering
* An LLMwiki implementation that uses macOS filesystem extensions to reflect a SQLite database into a local filesystem for agents to traverse (basically the new macOS way to do FUSE, fussy enough that the app has to run out of /Applications to work)
* An agent-driven Music.app replacement with AirPlay streaming support managing Apple Music catalogs and tracks
All of these have worked without me ever once touching the xcode UI, except one time to generate Apple Developer certificates, which I then drove exclusively from CLI tools.
These are all pretty modern SwiftUI applications.
Areas they could improve - what ever the heck is going on in their diffing ui for source control.
The most useful one is a little weather sparkline to show local temperature forecasts. Useful every day.
honestly would not even know what to do / click in xcode
* and giving Anthropic all your secrets, env vars, certificates and your source code to them.
You can even run simulators in it if you choose the Tart backend.
Anthropic isn't great at keeping stuff private as we saw with the Claude Code leak. The more information they get access to the bigger the risk.
But also yes this is a real concern.
Useful sanity check!
You can use `--password <password>` (yeah, yeah, passwords on the command line are bad; I'm just challenging "there's no way around the prompt.")
Later (contradicting itself):
> The one step that stays interactive is notarytool store-credentials, and that’s a choice rather than a limitation: you could pass --password and script it, but that means putting an app-specific password in your shell history.
You can configure your shell to ignore history when needed.
https://stackoverflow.com/questions/6475524/how-do-i-prevent...
https://superuser.com/questions/352788/how-to-prevent-a-comm...
(And if it's in a script, it won't be in your history anyway.)
> These passwords go stale silently whenever you change your Apple ID password
You can use an ASC API key instead. This can be either a team key ("developer" access level) or an individual key.
https://developer.apple.com/documentation/appstoreconnectapi...
"Next, open Xcode"
Upgrading from one version to the next especially so.
Expo seems to shield you from a lot of issues, without really taking any power away from you either.
https://github.com/inopinatus/sublime_url/blob/main/Makefile
Same shit with a bit of structure.
IDEs are dead, prove me wrong.