* fix(control-socket): recover from transient accept errors
Keep the control socket alive when Darwin reports a transient fcntl failure while accepting a client connection.
Refs #1346
* test(control-socket): use synchronous pipeline lookup
* test(control-socket): query handler on event loop
* fix(control-socket): preserve channel backpressure
* test(control-socket): exercise accept error recovery
* test(control-socket): verify accepts continue after transient errors
Check that the real server inbound stream yields a client connection after
each simulated accept error. Keep the read-routing and unrelated-error
checks, bound the wait, and close the test connections.
Adapted from the regression test suggested in:
https://github.com/openai/tart/pull/1351#issuecomment-5851285933
---------
Co-authored-by: Yibo Zhuang <yzhuang@openai.com>
* Bundle Swift compatibility libraries in Tart releases
* fix(release): weak-link Swift compatibility library
* fix(release): weak-link from Xcode 27 library path
* fix(storage): preserve running VM delete errors
Do not reinterpret RuntimeError.VMIsRunning as a missing VM when the storage wrapper bridges errors through NSError.
Refs #1345
* test(storage): initialize running VM lock file
* test(storage): hold VM lock in a child process
* fix(storage): narrow file-not-found error matching
* test(storage): use Swift error-domain regression coverage
* Allow HumanReadableByteCount to represent an unknown byte count
Some byte counts, such as the capacity of an ASIF disk image, can't
always be determined. Accept an optional byte count and render an
unknown value as "-" in text output and as null in JSON output.
* Fix listing and getting VMs when disk capacity is unavailable
For ASIF disk images, the disk capacity is read with "diskutil image
info". The command fails with "Resource temporarily unavailable" while
a running VM holds the disk image open. As a result, "tart list" and
"tart get" fail entirely when any such VM exists.
Treat the disk capacity as unknown when it can't be determined, so
that both commands still show the remaining information.
Fixes#1344
* Remove unused VMDirectory.diskSizeGB()
The method was added together with diskSizeBytes() but has never been
used. "tart list" and "tart get" use diskSizeBytes() directly.
* Use registry-1.docker.io for Docker Hub's docker.io host
docker.io doesn't serve the registry API: https://docker.io/v2/ redirects
to https://www.docker.com/, which URLSession follows, getting back an HTML
page with HTTP 200. As a result, pushing fails with
UnexpectedHTTPStatusCode("pushing blob (POST)", 200, ...), pulling fails
to parse the manifest and "tart login docker.io" accepts any credentials,
because ping() never gets an authentication challenge.
Send the API requests for docker.io to registry-1.docker.io instead, while
still using docker.io for the pushed image names and for the credentials
lookup, so that credentials saved with "tart login docker.io" keep working.
Fixes#1275
* Match docker.io case insensitively
* Recognize Docker Hub with an explicit port
* Parse the registry host once and keep it normalized
Using the host exactly as specified for naming and credentials lookup
changed the behavior for other registries too. For example,
"127.0.0.1:05000" used to find credentials stored for "127.0.0.1:5000",
but didn't anymore.
Parse the URL once instead, take the normalized host and port from it
like before, and only replace the URL's host with registry-1.docker.io
for Docker Hub.
VZVirtualMachine asserts that it is used on the queue it was created with,
which for tart is the main queue. Since #1262 replaced the unstructured Task
in "tart run"'s SIGUSR2 handler with ErrorReportingTask, that assertion fails:
Task.init carries @_inheritActorContext, but ErrorReportingTask.init did not,
so an operation written inside MainActor-isolated Run.runOnMainThread() is formed
in a nonisolated init and runs on the cooperative pool, not the main queue.
The result is that asking a VM to stop gracefully kills it instead. Sending
SIGUSR2, which #842 hooked to requestStop() for exactly this purpose, crashes
the process:
Thread 1 queue: com.apple.root.default-qos.cooperative
_dispatch_assert_queue_fail
dispatch_assert_queue
-[VZVirtualMachine requestStopWithError:]
closure in Run.runOnMainThread()
closure in ErrorReportingTask.init(_:operation:)
The guest then loses power without a chance to flush, and on a Linux guest
with ext4's default delayed allocation that discards whatever had not been
written back yet. The same applies to the requestStop() in
applicationShouldTerminate(), i.e. closing the window of a VM run with a GUI.
Give the operation the same @_inheritActorContext that Task.init has, so that
wrapping a call in ErrorReportingTask no longer changes where it runs.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This is first of several changes to add support for the new
DiskImageKit ASIF layers to tart VM images.
This change is focused on laying down the OCI media type for
ASIF layers, content addressable store structure, as well
as the VMDirectory structure for supporting layers.
Add DiskImageStack type to model VM image using DiskImage APIs.
Unregister the stdin readabilityHandler when availableData returns empty:
a closed pipe fd stays permanently readable, so Foundation re-invokes the
handler in a tight loop (fstat + zero-byte read) at 100% of one core for
the rest of the command's lifetime.
Fixes#1280
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Publish Tart to openai/homebrew-tools
* Write Tart formula under Formula directory
* Use macOS 26 runners
* docs: install Tart tools from OpenAI tap
* Add required GitHub Actions test check
* Fix hosted tests and notarization credentials
GoReleaser's Homebrew template always emits a bare `depends_on :macos`
for macOS-only formulae. Combining that with the `depends_on :macos =>
:ventura` line injected via custom_block triggers a Homebrew deprecation
warning on `brew upgrade`:
Warning: Calling `depends_on :macos` with `depends_on macos:` is deprecated! Use `depends_on :macos` with `depends_on macos:` inside an `on_macos` block instead.
Please report this issue to the cirruslabs/homebrew-cli tap (not Homebrew/* repositories), or even better, submit a PR to fix it:
/opt/homebrew/Library/Taps/cirruslabs/homebrew-cli/tart.rb:22
Declaring the version constraint inside an `on_macos` block is the form
Homebrew recommends and silences the warning without changing behavior
(still macOS-only, Ventura or newer).
* Use let for the immutable disk image storage attachment
* Don't bind the unused error when catching connection-pool failures
* Report errors thrown inside tart run's fire-and-forget tasks
We were discarding any error thrown inside these unstructured tasks,
which silently hid failures to run the control socket or to start and
stop the VM, and which the compiler now warns about.
Wrap them in an ErrorReportingTask, which spawns the task and reports
any thrown error to stderr, rather than repeating a do/catch at every
call site. An unstructured task spawned from a synchronous context (a
signal handler or SwiftUI action) has no parent to propagate the error
to, so reporting it is the best we can do.
* Avoid blocking SwiftNIO calls in async guest agent connections
The gRPC channel setup in "tart exec" and the MAC address resolver
created a dedicated event loop group and tore both it and the channel
down with the blocking syncShutdownGracefully() and wait(), which are
unavailable from async contexts (the former is an error in the Swift 6
language mode).
Factor the connection out into a withGuestAgentChannel() helper that
uses the process-wide singleton event loop group, so there is no group
to shut down, and closes the channel with the async close().get().
Exposes Apple's macOS 27 guest provisioning API
(VZMacGuestProvisioningOptions) so a macOS guest can be set up
automatically on the first boot after restore.
The flag takes a comma-separated list of key=value pairs mapping 1:1 to
the API properties (fullName, username, password, logsInAutomatically,
enablesRemoteLogin). It is validated to require a macOS 27+ host and a
macOS VM.
The entire user-facing surface is gated behind
'#if arch(arm64) && compiler(>=6.4)' so the flag doesn't appear in help
on toolchains that lack the macOS 27 SDK, while the runtime
'#available(macOS 27, *)' check gates actual use against the host OS.
When built against the macOS 27 (Xcode 27, Swift 6.4) SDK, "tart run"
brings up the VM window but the guest never boots.
Swift's asynchronous main() entry point implicitly starts an executor
that owns the main thread, and as of Swift 6.4 that executor is no
longer backed by the Dispatch main queue. Running an AppKit/SwiftUI
run loop nested inside it via MainApp.main() leaves the main run loop
unable to drain Swift tasks or DispatchQueue.main, so the task that
starts the VM is never scheduled, even though the window itself
(driven directly by AppKit during launch) still appears.
We now keep Root.main() synchronous, so that a command driving a run
loop can own the main thread at the top level, exactly like a plain
SwiftUI app. With AppKit owning the loop again, MainActor tasks and
the Dispatch main queue drain as before. Such commands opt in through
a new MainThreadCommand protocol; everything else keeps running
asynchronously via a detached task and dispatchMain().
Verified that the guest boots again, and that Ctrl+C still stops the
VM gracefully.
* Remove disk v1 support
* fix: address PR review feedback
- add explicit error for legacy disk.v1 media type during pull
- include actionable re-push guidance in runtime error
🤖 Generated with [Codex](https://chatgpt.com/codex)
Co-Authored-By: Codex <codex@openai.com>
* Re-use legacyDiskV1MediaType in error message
---------
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Nikolay Edigaryev <edigaryev@gmail.com>