feat(api): surface lastExitCode on ContainerSnapshot#1503
Draft
chrisgeo wants to merge 1 commit intoapple:mainfrom
Draft
feat(api): surface lastExitCode on ContainerSnapshot#1503chrisgeo wants to merge 1 commit intoapple:mainfrom
chrisgeo wants to merge 1 commit intoapple:mainfrom
Conversation
Adds an optional lastExitCode: Int32? field to ContainerSnapshot,
populated in ContainersService.handleContainerExit when the container
transitions to .stopped. The exit code is taken from the existing
ExitMonitor callback's ExitStatus value.
Motivation
----------
External orchestrators that drive the API server (Docker-Compose-style
service ordering is the canonical use case) need to distinguish between
'container exited cleanly' and 'container exited but the host gave up
on it'. The compose-spec depends_on: condition: service_completed_successfully
gate is exactly this distinction. Today, ContainerSnapshot exposes
.stopped without an exit code, so consumers can only fall back to a
stopped-on-time check and silently misinterpret a non-zero exit as
success.
Wire compatibility
------------------
ContainerSnapshot is marshaled as Codable JSON over XPC. Adding an
optional field is forward-compatible:
- Older clients reading from a newer server: ignore the new key.
- Newer clients reading from an older server: decode lastExitCode
as nil (which the doc comment already documents as 'never exited
or exit not captured').
Scope
-----
In-memory only. The exit code lives in the in-memory ContainerState
snapshot for the duration of apiserver uptime. A daemon restart resets
all snapshots to .stopped without exit codes (existing behavior). A
follow-up patch can add bundle persistence (exit_status.json) if the
daemon-restart case becomes operationally relevant.
Files
-----
- Sources/ContainerResource/Container/ContainerSnapshot.swift: new
field + init parameter (default nil for backward compat at all
existing construction sites; SandboxService.swift's snapshot
constructor is unchanged).
- Sources/Services/ContainerAPIService/Server/Containers/ContainersService.swift:
one assignment in handleContainerExit's terminal state block, taking
exitCode from the existing 'code: ExitStatus?' parameter.
This was referenced May 2, 2026
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.
Companion issue: #1501
Type of Change
Motivation and Context
ContainerSnapshottoday exposesRuntimeStatus.stoppedbut not the underlying exit code. External orchestrators (e.g. a Compose-spec orchestrator implementingdepends_on: condition: service_completed_successfully) need to distinguish clean exit (exit code 0) from failed exit (non-zero) of a one-shot container. Without this, they have to treat.stoppedas success and silently misinterpret non-zero exits.The data already exists internally:
ContainersService.handleContainerExit(id:code:context:)receives a non-nilExitStatus?from the existingExitMonitorcallback wiring whenever a container actually exits via the runtime. This PR just stamps that value onto the snapshot.See #1501 for the full motivation and design notes.
What this PR changes
Sources/ContainerResource/Container/ContainerSnapshot.swift: new optionallastExitCode: Int32?field with a defaulted init parameter so all existing construction sites compile unchanged.Sources/Services/ContainerAPIService/Server/Containers/ContainersService.swift: one assignment in the terminal-state block ofhandleContainerExit, takingexitCodefrom the existingcode: ExitStatus?parameter.Total: 2 files, +8/-1.
Wire compatibility
ContainerSnapshotis marshaled asCodableJSON over XPC. Adding an optional field is forward-compatible:lastExitCodeasnil— the documented "never exited or exit not captured" case.Scope (deliberately minimal)
In-memory only. The exit code lives in the in-memory
ContainerStatesnapshot for API-server uptime. A daemon restart resets all snapshots to.stoppedwithout exit codes (existing behavior). Bundle persistence (e.g.exit_status.json) is a deliberate out-of-scope follow-up.Testing
swift buildclean on macOS 26 / Apple silicon, including downstream targets that consumeContainerSnapshot).ContainerSnapshotTestsinTests/ContainerResourceTests/. Happy to add a Codable round-trip test if maintainers prefer; opened as draft to confirm scope first.Status
Draft. Filed alongside #1501. Marked draft until a maintainer confirms the proposed surface is acceptable; will mark ready and add tests on request.