Skip to content

Support suspending and resuming Linux VMs - #1297

Merged
yzhuang-oai merged 1 commit into
openai:mainfrom
randvoorhies:linux-suspend-resume
Oct 15, 2026
Merged

yzhuang-oai merged 1 commit into
openai:mainfrom
randvoorhies:linux-suspend-resume

Conversation

@randvoorhies

@randvoorhies randvoorhies commented Jul 28, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #1177

Summary

Previous investigation in #796 and #1177 implied that restoring suspended Linux VMs wasn't supported by Virtualization.framework. Some testing on current macOS shows that Linux restoration does actually work properly, and that Tart's existing suspend/restore mechanisms were very close to working.

The Darwin platform implementation persists the VM's ECID and assigns it to VZMacPlatformConfiguration.machineIdentifier. However, the Linux platform didn't specify a machineIdentifier when constructing its VZGenericPlatformConfiguration, which caused a new identifier to be generated each time the platform was instantiated.

Unfortunately, Virtualization.framework returns a very opaque error if saved machine state is restored with a mismatched identifier:

Error Domain=VZErrorDomain Code=12 "The virtual machine failed to restore with error “invalid argument”."

This PR generates and persists a VZGenericMachineIdentifier in new Linux VM config files, then reuses it whenever the platform is constructed.

Apple's SDK header for VZGenericPlatformConfiguration.machineIdentifier says that running two VMs concurrently with the same identifier results in undefined behavior in the guest, and that restoring saved state requires the identifier the state was saved with. Now that identifiers are persisted, they're handled the same way Tart already handles MAC addresses:

  • tart clone of a non-suspended Linux VM gives the clone a new identifier. Clones of suspended VMs keep the source's identifier, because their saved state can only be restored with it.
  • tart run gives a VM a new identifier (discarding any saved state) if another running VM already has the same one, mirroring the existing MAC collision check. This covers clones of suspended VMs and duplicate imports. The config is read under the global storage lock, so concurrent runs of the same VM can't act on a stale identifier.

Darwin ECIDs are intentionally left as they are, because changing the ECID of an installed macOS guest can stop it from booting (#59, #977). Linux guests previously got a new identifier on every boot, so regenerating one is safe.

Existing Linux configs without a machineIdentifier are loadable for normal use but can't be suspended. Cloning such a VM gives the clone an identifier.

The Linux platform now conforms to PlatformSuspendable. Suspendable Linux VMs retain USB keyboard support but omit VZUSBScreenCoordinatePointingDeviceConfiguration, which passes validateSaveRestoreSupport() but causes restoration to fail.

Testing

  • The full Swift test suite passes.
  • Unit tests cover identifier serialization and platform reconstruction, legacy configuration compatibility, and identifier regeneration (replacing an existing identifier, assigning a missing one, and discarding saved state).
  • A current Ubuntu OCI image without an identifier was cloned and verified to receive one before its first run.
  • The resulting Ubuntu VM was started, suspended, and restored while streaming an in-memory counter over UDP to the host:
boot=d055e850-7951-4744-8790-380df1fa628c pid=1031 count=109
boot=d055e850-7951-4744-8790-380df1fa628c pid=1031 count=110
boot=d055e850-7951-4744-8790-380df1fa628c pid=1031 count=111
# VM suspended
# VM restored
boot=d055e850-7951-4744-8790-380df1fa628c pid=1031 count=112
boot=d055e850-7951-4744-8790-380df1fa628c pid=1031 count=113

The matching boot ID and PID with a continuously incrementing counter confirm that saved machine state was restored rather than cold-booted.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0d2a3fcca1

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread Sources/tart/VMDirectory.swift Outdated
@randvoorhies
randvoorhies force-pushed the linux-suspend-resume branch from 0d2a3fc to 769f6a2 Compare July 28, 2026 23:52
@randvoorhies

Copy link
Copy Markdown
Contributor Author

I'm very excited about getting this merged in. My org has a suite of playwright tests that run against a containerized stack of services running on a Linux host. Bringing that stack up and down to reset state for each test is expensive at around 30s per respin. Some initial experiments with code behind this new PR show that we can take a snapshot of a VM running our system, and then resume it in ~2s before each test.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 769f6a21be

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread Sources/tart/Commands/Push.swift Outdated
@randvoorhies
randvoorhies force-pushed the linux-suspend-resume branch from 769f6a2 to 01d9359 Compare July 31, 2026 20:06
@chris-rock

Copy link
Copy Markdown

Would be great to get this in

@yzhuang-oai

Copy link
Copy Markdown
Contributor

@randvoorhies can you rebase and I will review

@randvoorhies

Copy link
Copy Markdown
Contributor Author

@yzhuang-oai yup no problem I'll get to it in the next few days

@randvoorhies

Copy link
Copy Markdown
Contributor Author

Ok @yzhuang-oai this MR is rebased and should be good to go.

Comment thread Sources/tart/Commands/Clone.swift Outdated
@yzhuang-oai

Copy link
Copy Markdown
Contributor

@randvoorhies can you check the review comment? almost ready to merge.

@randvoorhies

randvoorhies commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor Author

@yzhuang-oai updated and rebased onto the latest main. The machineIdentifier now gets regenerated in the same situations as the MAC address.

  • tart clone on a non-suspended Linux VM makes a new id for the clone. Suspended clones keep their ids, because saved state can only be restored with the id it was saved with.
  • tart run regenerates the id if it collides with another running VM (discarding any saved state). This covers clones of suspended VMs and duplicate imports.

I had originally wanted to follow how ecid is handled for Darwin guests because the machineIdentifier felt like the same concept. However, it sounds like changing the ecid for some versions of macOS guests can sometimes stop them from booting (#59, #977) so preserving it makes sense there. The code in main generates a new machineIdentifier on every Linux boot, so Linux guests clearly don't mind it changing.

Running several VMs with the same machineIdentifier may or may not cause real problems, but Apple says it's undefined behavior in the VZGenericPlatformConfiguration.h SDK header so you're right it's better to play it safe.

@randvoorhies

Copy link
Copy Markdown
Contributor Author

Hey @yzhuang-oai do you need anything more from me before reviewing/merging?

Comment thread Sources/tart/Commands/Run.swift
Restoring saved state requires the VZGenericMachineIdentifier that the
VM was saved with, but Linux VMs were getting a new one every time they
started (including when trying to restore from saved state). The
identifier is now persisted in the VM config and reused on every start.

Apple's SDK header for VZGenericPlatformConfiguration.machineIdentifier
says:

> Running two virtual machines concurrently with the same identifier
> results in undefined behavior in the guest operating system. When
> restoring a virtual machine from saved state, this `machineIdentifier`
> must match the `machineIdentifier` of the saved virtual machine.

So identifiers for Linux guests are now treated like MAC addresses:
- Cloning a non-suspended VM generates a new id for the clone.
  - Suspended clones keep their id because their saved state requires
    it.
- Running a VM whose id matches an already running VM generates a new
  id.
  - If the colliding VM being run was suspended, its saved state will
    be discarded.

Configurations without an identifier will still load, but can't be
suspended.

Suspendable mode for Linux VMs omits the pointing device because
VZUSBScreenCoordinatePointingDeviceConfiguration makes restores fail.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-15T20:04:06.779268Z d9e347d New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d9e347d914

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread Sources/tart/Commands/Clone.swift

@yzhuang-oai yzhuang-oai left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm thanks @randvoorhies for all the work on this!!

@yzhuang-oai
yzhuang-oai merged commit 3207aa6 into openai:main Oct 15, 2026
1 check passed
@randvoorhies

Copy link
Copy Markdown
Contributor Author

You bet, thanks for the great feedback!

@randvoorhies

Copy link
Copy Markdown
Contributor Author

Hey @yzhuang-oai btw I was about to finish up some final polish to sneak in before the merge. If you want, I can open another quick PR for the following:

  • In Run.swift, if a user tries to run an already running VM with different case, e.g. ubuntu vs uBuNtU, the current code will overwrite the running VM's config
  • In Run.swift's validate(), we should throw an error if --no-usb-accessories is passed with --suspendable, since resuming without the same flag fails
  • A couple of tart clone tests for the new machine identifier behavior

Happy to do it, or happy to move on just lmk.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feature request: suspend/resume linux vm via --suspendable flag

3 participants