apple / apple/container

[Request]: Surface lastExitCode on ContainerSnapshot

Open
#1,501 2 comments 1 reaction 0 assignees View on GitHub
Dominant language
Swift
Stars
49.9k
Forks
1.8k
Avg merge
1d 20h
Merged PRs (30d)
22

Description

> _Suggested triage: type=`Feature`, label=`enhancement`. (External contributor — cannot self-apply.)_

### Feature or enhancement request details

#### Summary

Add an optional `lastExitCode: Int32?` field to `ContainerSnapshot`, populated from the existing `ExitMonitor` callback when a container transitions to `.stopped`.

A draft PR with the implementation is open at #1503.

#### Motivation

External orchestrators that drive the API server (the canonical use case is a Compose-spec orchestrator implementing `depends_on: condition: service_completed_successfully`) need to distinguish between *clean* exit (exit code 0) and *failed* exit (non-zero exit code) of a one-shot container.

Today `ContainerSnapshot` exposes `RuntimeStatus.stopped`, but the underlying exit code is not surfaced. The orchestrator can observe \"the container stopped\" but not \"the container stopped successfully\". This forces consumers to treat `.stopped` as a success, silently misinterpreting non-zero exits.

The same data already exists in the daemon's path: `ContainersService.handleContainerExit(id:code:context:)` receives `code: ExitStatus?` from the existing `ExitMonitor` callback wiring. The proposed change just stamps that value onto the snapshot.

#### Proposed change

```swift
// Sources/ContainerResource/Container/ContainerSnapshot.swift
public var lastExitCode: Int32?

public init(
configuration: ContainerConfiguration,
status: RuntimeStatus,
networks: [Attachment],
startedDate: Date? = nil,
lastExitCode: Int32? = nil
) { ... }
```

```swift
// Sources/Services/ContainerAPIService/Server/Containers/ContainersService.swift
// In handleContainerExit, terminal-state block:
state.snapshot.status = .stopped
state.snapshot.lastExitCode = code?.exitCode // NEW
state.snapshot.networks = []
```

#### 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` (the documented \"never exited or exit not captured\" case).

#### Scope

Intentionally minimal — *in-memory only*. The exit code lives in the in-memory `ContainerState` snapshot for the duration of API server uptime. A daemon restart resets all snapshots to `.stopped` without exit codes (existing behavior). Bundle persistence for survival across daemon restarts (e.g. an `exit_status.json`) is a deliberate out-of-scope follow-up.

#### Use case context

This is a straightforward upstream-ing of a small change we have been carrying in a downstream fork to power Compose-style orchestration on top of `apple/container` ([container-compose](https://github.com/full-chaos/container-compose)). We would much prefer to consume the field directly from `apple/container`'s `ContainerSnapshot` rather than maintain a fork. We have existing internal coverage of this change against a real `apple/container` daemon; happy to share notes.

### Code of Conduct

I agree to follow this project's Code of Conduct.

Contributor guide

Open the contributing guide

Research direction

Start with Sources/ContainerResource/Container/ContainerSnapshot.swift and Sources/Services/ContainerAPIService/Server/Containers/ContainersService.swift, then trace handleContainerExit and the existing ExitMonitor callback. Done means the optional exit code is exposed on stopped snapshots while preserving nil for uncaptured exits and Codable compatibility; coordinate with draft PR #1503.

Written by the indexing model from the issue text.

Assessment

Tech stack
swift
Domain
api, backend
Issue type
Feature
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.