fix: return None from image_created_at when timestamp is absent
test / integration (pull_request) Successful in 14s
test / coverage (pull_request) Successful in 41s
lint / lint (push) Failing after 45s
test / unit (pull_request) Successful in 1m37s

FROM-scratch images (commit_container output) and registry images built
for reproducibility often omit the created field entirely. Both backends
were calling die() in that case, crashing the cached-image quickstart
before any container started.

image_created_at now returns datetime | None — None when the field is
absent or unparseable, die() only on real inspect failures (non-zero
exit, malformed JSON). stale_checks in both backends skips the staleness
check when None is returned.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-07-19 01:04:25 +00:00
parent fa3e45cc54
commit 6082e92b46
6 changed files with 45 additions and 34 deletions
+6 -2
View File
@@ -231,7 +231,11 @@ def stale_checks(plan: DockerBottlePlan) -> None:
return return
committed = read_committed_image(plan.slug) committed = read_committed_image(plan.slug)
if committed and docker_mod.image_exists(committed): if committed and docker_mod.image_exists(committed):
check_stale(f"agent image {committed!r}", docker_mod.image_created_at(committed)) ts = docker_mod.image_created_at(committed)
if ts is not None:
check_stale(f"agent image {committed!r}", ts)
return return
if docker_mod.image_exists(plan.image): if docker_mod.image_exists(plan.image):
check_stale(f"agent image {plan.image!r}", docker_mod.image_created_at(plan.image)) ts = docker_mod.image_created_at(plan.image)
if ts is not None:
check_stale(f"agent image {plan.image!r}", ts)
+7 -3
View File
@@ -200,8 +200,10 @@ def commit_container(container_name: str, image_tag: str) -> None:
info(f"committed {container_name!r}{image_tag!r}") info(f"committed {container_name!r}{image_tag!r}")
def image_created_at(ref: str) -> datetime: def image_created_at(ref: str) -> datetime | None:
"""Return Docker's image Created timestamp as an aware UTC datetime.""" """Return Docker's image Created timestamp as an aware UTC datetime, or
None when the field is absent or unparseable. Callers should skip the
stale check when None is returned."""
r = subprocess.run( r = subprocess.run(
["docker", "image", "inspect", "--format", "{{.Created}}", ref], ["docker", "image", "inspect", "--format", "{{.Created}}", ref],
capture_output=True, capture_output=True,
@@ -214,10 +216,12 @@ def image_created_at(ref: str) -> datetime:
f"{(r.stderr or '').strip() or '<no stderr>'}" f"{(r.stderr or '').strip() or '<no stderr>'}"
) )
raw = r.stdout.strip() raw = r.stdout.strip()
if not raw:
return None
try: try:
return _parse_docker_timestamp(raw) return _parse_docker_timestamp(raw)
except ValueError: except ValueError:
die(f"docker image inspect for {ref!r} returned invalid Created timestamp: {raw!r}") return None
def _parse_docker_timestamp(raw: str) -> datetime: def _parse_docker_timestamp(raw: str) -> datetime:
+6 -2
View File
@@ -211,10 +211,14 @@ def stale_checks(plan: MacosContainerBottlePlan) -> None:
return return
committed = read_committed_image(plan.slug) committed = read_committed_image(plan.slug)
if committed and container_mod.image_exists(committed): if committed and container_mod.image_exists(committed):
check_stale(f"agent image {committed!r}", container_mod.image_created_at(committed)) ts = container_mod.image_created_at(committed)
if ts is not None:
check_stale(f"agent image {committed!r}", ts)
return return
if container_mod.image_exists(plan.image): if container_mod.image_exists(plan.image):
check_stale(f"agent image {plan.image!r}", container_mod.image_created_at(plan.image)) ts = container_mod.image_created_at(plan.image)
if ts is not None:
check_stale(f"agent image {plan.image!r}", ts)
def _provision_git_gate_keys( def _provision_git_gate_keys(
+6 -6
View File
@@ -612,10 +612,11 @@ def image_id(ref: str) -> str:
raise AssertionError("unreachable") raise AssertionError("unreachable")
def image_created_at(ref: str) -> datetime: def image_created_at(ref: str) -> datetime | None:
"""Return the image creation timestamp as an aware UTC datetime. """Return the image creation timestamp as an aware UTC datetime, or None
when the field is absent or unparseable (e.g. FROM-scratch images, images
Parses the `created` field from `container image inspect` JSON output.""" pulled from registries that omit the field). Callers should skip the stale
check when None is returned rather than treating it as an error."""
result = subprocess.run( result = subprocess.run(
[_CONTAINER, "image", "inspect", ref], [_CONTAINER, "image", "inspect", ref],
capture_output=True, capture_output=True,
@@ -641,8 +642,7 @@ def image_created_at(ref: str) -> datetime:
return datetime.fromisoformat(ts).replace(tzinfo=timezone.utc) return datetime.fromisoformat(ts).replace(tzinfo=timezone.utc)
except ValueError: except ValueError:
pass pass
die(f"container image inspect for {ref!r} did not include a creation timestamp") return None
raise AssertionError("unreachable")
def save(ref: str, output: str) -> None: def save(ref: str, output: str) -> None:
+12 -8
View File
@@ -54,17 +54,21 @@ class TestImageCreatedAt(unittest.TestCase):
die.assert_called_once() die.assert_called_once()
self.assertIn("missing:tag", die.call_args.args[0]) self.assertIn("missing:tag", die.call_args.args[0])
def test_dies_on_invalid_timestamp(self): def test_returns_none_on_invalid_timestamp(self):
with patch.object( with patch.object(
docker_mod.subprocess, "run", docker_mod.subprocess, "run",
return_value=_ok(stdout="not-a-timestamp\n"), return_value=_ok(stdout="not-a-timestamp\n"),
), patch.object( ):
docker_mod, "die", side_effect=SystemExit("die"), result = docker_mod.image_created_at("some:tag")
) as die: self.assertIsNone(result)
with self.assertRaises(SystemExit):
docker_mod.image_created_at("some:tag") def test_returns_none_on_empty_stdout(self):
die.assert_called_once() with patch.object(
self.assertIn("some:tag", die.call_args.args[0]) docker_mod.subprocess, "run",
return_value=_ok(stdout=""),
):
result = docker_mod.image_created_at("some:tag")
self.assertIsNone(result)
def test_parse_docker_timestamp_no_tzinfo_defaults_to_utc(self): def test_parse_docker_timestamp_no_tzinfo_defaults_to_utc(self):
# A bare datetime with no tz offset should be treated as UTC. # A bare datetime with no tz offset should be treated as UTC.
+8 -13
View File
@@ -371,22 +371,17 @@ class TestMacosContainerImageCreatedAt(unittest.TestCase):
util.image_created_at("some:tag") util.image_created_at("some:tag")
die.assert_called_once() die.assert_called_once()
def test_dies_when_no_created_field(self): def test_returns_none_when_no_created_field(self):
payload = '[{"id": "sha256:abc123"}]' payload = '[{"id": "sha256:abc123"}]'
with patch.object(util.subprocess, "run", return_value=self._ok(payload)), \ with patch.object(util.subprocess, "run", return_value=self._ok(payload)):
patch.object(util, "die", side_effect=SystemExit("die")) as die: result = util.image_created_at("some:tag")
with self.assertRaises(SystemExit): self.assertIsNone(result)
util.image_created_at("some:tag")
die.assert_called_once()
self.assertIn("creation timestamp", die.call_args.args[0])
def test_dies_on_invalid_timestamp_format(self): def test_returns_none_on_invalid_timestamp_format(self):
payload = '[{"created": "not-a-date"}]' payload = '[{"created": "not-a-date"}]'
with patch.object(util.subprocess, "run", return_value=self._ok(payload)), \ with patch.object(util.subprocess, "run", return_value=self._ok(payload)):
patch.object(util, "die", side_effect=SystemExit("die")) as die: result = util.image_created_at("some:tag")
with self.assertRaises(SystemExit): self.assertIsNone(result)
util.image_created_at("some:tag")
die.assert_called_once()
if __name__ == "__main__": if __name__ == "__main__":