Skip to content

Add SnapshotableContainer protocol and Docker snapshot implementation#94

Open
joshgreaves wants to merge 1 commit intomainfrom
go-explore/snapshotable-container
Open

Add SnapshotableContainer protocol and Docker snapshot implementation#94
joshgreaves wants to merge 1 commit intomainfrom
go-explore/snapshotable-container

Conversation

@joshgreaves
Copy link
Copy Markdown
Contributor

@joshgreaves joshgreaves commented Mar 13, 2026

User description

Summary

  • Adds SnapshotableContainer protocol extending Container with snapshot(), from_snapshot(), delete_snapshot(), and delete_snapshot_sync() methods
  • Implements snapshotting on DockerContainer using docker commit to capture filesystem state as tagged images
  • Exports SnapshotableContainer from ares.containers
  • Fixes pre-existing duplicate preset registration bug in presets.py

Context

Part of Go-Explore support (Wave 1). This PR is independent of the other two Wave 1 PRs and can be reviewed/merged in parallel:

  • PR: Agent state serialization (go-explore/agent-state-serialization)
  • PR: EnvironmentCheckpoint protocol (go-explore/checkpoint-protocol)

Daytona support is not included -- their runtime sandbox snapshotting API is not yet available (daytonaio/daytona#2519). The SnapshotableContainer protocol is designed so Daytona support slots in without changes to consumers.

Tests

6 new tests covering snapshot creation, error handling, from_snapshot, deletion, and idempotent deletion.


Generated description

Below is a concise technical summary of the changes proposed in this PR:
Introduce the SnapshotableContainer protocol in ares.containers.containers and update DockerContainer to capture filesystem snapshots via Docker images so Go-Explore workflows can restore prior environment states. Fix duplicate Harbor preset registration in presets.py to ensure each built-in preset is registered only once.

TopicDetails
Container snapshots Enable SnapshotableContainer and DockerContainer snapshot lifecycle so Go-Explore flows can capture and restore filesystem state.
Modified files (4)
  • src/ares/containers/__init__.py
  • src/ares/containers/containers.py
  • src/ares/containers/docker.py
  • src/ares/containers/docker_snapshot_test.py
Latest Contributors(2)
UserCommitDate
joshua.greaves@gmail.comAdd-an-LLMResponse-mod...January 29, 2026
ryan@withmartian.comFix-for-Docker-build-a...January 28, 2026
Preset registration Fix duplicate Harbor preset registration by tracking already-registered names before calling the registry.
Modified files (1)
  • src/ares/presets.py
Latest Contributors(2)
UserCommitDate
Narmeen07Add-mechanistic-interp...February 19, 2026
ryan@withmartian.comRemoved-repeated-indiv...January 29, 2026
This pull request is reviewed by Baz. Review like a pro on (Baz).

Summary by CodeRabbit

  • New Features

    • Added container snapshotting: capture filesystem snapshots, restore containers from snapshots, and delete snapshots; snapshot API is now publicly exposed.
  • Bug Fixes

    • Prevented duplicate preset registrations by deduplicating presets before registering.
  • Tests

    • Added tests for snapshot creation, restore/from-snapshot, deletion (including not-found handling), and synchronous cleanup.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 13, 2026

Warning

Rate limit exceeded

@gowthamkishore3 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 9 minutes and 49 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7cb883c1-f9bc-4664-939c-496c9b56656e

📥 Commits

Reviewing files that changed from the base of the PR and between c804aa2 and 49b5f74.

📒 Files selected for processing (5)
  • src/ares/containers/__init__.py
  • src/ares/containers/containers.py
  • src/ares/containers/docker.py
  • src/ares/containers/docker_snapshot_test.py
  • src/ares/presets.py
📝 Walkthrough

Walkthrough

Adds a SnapshotableContainer protocol and exposes it; implements snapshot/from_snapshot/delete_snapshot/delete_snapshot_sync in DockerContainer; adds unit tests for snapshot behavior; and deduplicates preset registration to avoid duplicate presets.

Changes

Cohort / File(s) Summary
Container Protocol Definition
src/ares/containers/containers.py
Adds SnapshotableContainer protocol with async snapshot() -> str, @classmethod from_snapshot(...), async delete_snapshot(...), and delete_snapshot_sync(...).
Container Module Exports
src/ares/containers/__init__.py
Exports SnapshotableContainer by importing it and adding it to __all__.
Docker Container Implementation
src/ares/containers/docker.py
DockerContainer now subclasses SnapshotableContainer; implements snapshot (commits container to image/tag), delete_snapshot/delete_snapshot_sync (remove image, handle ImageNotFound), and from_snapshot classmethod. Adds logging and small helpers.
Snapshot Tests
src/ares/containers/docker_snapshot_test.py
New pytest-asyncio tests covering snapshot creation, unstarted-container error, from_snapshot construction, async/sync deletion, and handling missing images.
Preset Deduplication
src/ares/presets.py
Adds local deduplication in _register_default_presets by tracking seen {dataset_id}-{code_agent_id} keys to avoid duplicate preset registrations.

Sequence Diagram(s)

sequenceDiagram
    participant T as Test / Client
    participant C as DockerContainer
    participant D as DockerClient
    participant I as ImageStore

    T->>C: snapshot()
    C->>D: docker commit/create image (tag)
    D-->>C: image_id (snapshot_id)
    C-->>T: return snapshot_id

    Note over T,C: restore from snapshot
    T->>C: from_snapshot(snapshot_id)
    C->>D: reference image(snapshot_id)
    D-->>C: image metadata
    C-->>T: new DockerContainer instance

    T->>C: delete_snapshot(snapshot_id)
    C->>D: remove image(snapshot_id)
    D-->>I: delete image (may be not-found)
    D-->>C: success / ImageNotFound handled
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped and boxed the filesystem clean,

Pinned a snap, a tiny image sheen.
From snap to life, back and again,
I tidy presets, no duplicates remain.
A carrot cheer for code made lean 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely summarizes the main changes: introducing a SnapshotableContainer protocol and implementing Docker snapshot functionality.
Docstring Coverage ✅ Passed Docstring coverage is 90.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch go-explore/snapshotable-container
📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

@mwilliammyers mwilliammyers marked this pull request as ready for review March 16, 2026 23:30
Comment on lines +191 to +194
async def snapshot(self) -> str:
"""Commit current container state as a Docker image."""
if self._container is None:
raise RuntimeError("Container not started, snapshot is not possible.")
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.

snapshot() raises raw RuntimeError when _container is None, violating the repo's error-handling guideline (CLAUDE.md) and preventing callers from distinguishing container lifecycle failures. Can we introduce or reuse a container-specific exception (e.g. ContainerNotStartedError) that derives from the repo exception hierarchy?

Finding type: AI Coding Guidelines | Severity: 🟢 Low


Want Baz to fix this for you? Activate Fixer

Other fix methods

Fix in Cursor

Prompt for AI Agents:

In src/ares/containers/docker.py around lines 191-194, the snapshot() method currently
raises a raw RuntimeError when self._container is None. Replace this with a
repo-specific container lifecycle exception (for example ContainerNotStartedError) that
derives from the project's error hierarchy described in CLAUDE.md. Add an import for
that exception at the top of the file (or create it in the appropriate exceptions module
if it doesn't exist), change the raise site to raise ContainerNotStartedError with a
clear message, and update any other lifecycle methods in this file that currently raise
RuntimeError to raise the same container-specific exception for consistency.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/ares/containers/docker_snapshot_test.py (1)

49-60: Exercise the restore path, not just field assignment.

This only checks constructor state. It still passes if from_snapshot() stops using the snapshot ID on start(), which is the contract consumers care about. Make this async and assert the mocked Docker client is started with the snapshot image.

🧪 Tighten the restore assertion
-def test_from_snapshot_creates_container():
+@pytest.mark.asyncio
+async def test_from_snapshot_creates_container(mock_docker_client):
     """Test that from_snapshot() creates a DockerContainer with the snapshot as image."""
     container = docker.DockerContainer.from_snapshot(
         "sha256:abc123",
         name="restored",
         default_workdir="/workspace",
@@
     assert isinstance(container, docker.DockerContainer)
     assert container.image == "sha256:abc123"
     assert container.name == "restored"
     assert container.default_workdir == "/workspace"
+
+    await container.start()
+    call_kwargs = mock_docker_client.containers.run.call_args
+    assert call_kwargs[1]["image"] == "sha256:abc123"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ares/containers/docker_snapshot_test.py` around lines 49 - 60, The test
only verifies field assignment; update test_from_snapshot_creates_container to
exercise the restore path by making it async, creating/mocking the Docker client
used by docker.DockerContainer (or the factory it uses), calling await
container.start() and asserting the mocked client's start/run method was invoked
with image "sha256:abc123" (or that the container was created/started using that
image); specifically target DockerContainer.from_snapshot and
DockerContainer.start to ensure the snapshot ID is actually used at start time
and assert the mock received the expected image argument.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/ares/containers/docker.py`:
- Around line 196-217: delete_snapshot and delete_snapshot_sync remove any image
ID given, so validate the ARES marker label ("ares-go-explore" == "true") before
calling images.remove to avoid deleting unrelated images; in delete_snapshot use
asyncio.to_thread to first fetch the image metadata (e.g.,
self._client.images.get or inspect) and check image.attrs['Config']['Labels']
(or image.labels) contains "ares-go-explore":"true" and only then call
images.remove (preserve the existing ImageNotFound handling and
_LOGGER.info/_LOGGER.debug behavior), and in delete_snapshot_sync do the same
synchronous inspect/get+label check before self._client.images.remove, using
contextlib.suppress(docker.errors.ImageNotFound) to keep idempotence; if the
label is absent or not matching, log a warning/debug and skip removal.

---

Nitpick comments:
In `@src/ares/containers/docker_snapshot_test.py`:
- Around line 49-60: The test only verifies field assignment; update
test_from_snapshot_creates_container to exercise the restore path by making it
async, creating/mocking the Docker client used by docker.DockerContainer (or the
factory it uses), calling await container.start() and asserting the mocked
client's start/run method was invoked with image "sha256:abc123" (or that the
container was created/started using that image); specifically target
DockerContainer.from_snapshot and DockerContainer.start to ensure the snapshot
ID is actually used at start time and assert the mock received the expected
image argument.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d3645b6d-39f5-4aca-a10b-1e8858cd0f97

📥 Commits

Reviewing files that changed from the base of the PR and between c804aa2 and 49b5f74.

📒 Files selected for processing (5)
  • src/ares/containers/__init__.py
  • src/ares/containers/containers.py
  • src/ares/containers/docker.py
  • src/ares/containers/docker_snapshot_test.py
  • src/ares/presets.py

Comment on lines +196 to +217
tag = f"ares-snapshot-{uuid.uuid4().hex[:12]}"
image = await asyncio.to_thread(
self._container.commit,
repository="ares-go-explore",
tag=tag,
conf={"Labels": {"ares-go-explore": "true"}},
)
_LOGGER.info("Snapshot created: %s (tag: %s)", image.id, tag)
return image.id

async def delete_snapshot(self, snapshot_id: str) -> None:
"""Delete a Docker image created by snapshot()."""
try:
await asyncio.to_thread(self._client.images.remove, snapshot_id)
_LOGGER.info("Snapshot deleted: %s", snapshot_id)
except docker.errors.ImageNotFound:
_LOGGER.debug("Snapshot %s already deleted.", snapshot_id)

def delete_snapshot_sync(self, snapshot_id: str) -> None:
"""Synchronous version for atexit cleanup."""
with contextlib.suppress(docker.errors.ImageNotFound):
self._client.images.remove(snapshot_id)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n src/ares/containers/docker.py | sed -n '190,225p'

Repository: withmartian/ares

Length of output: 1691


🏁 Script executed:

# Get more context around the snapshot-related methods
cat -n src/ares/containers/docker.py | sed -n '170,230p'

Repository: withmartian/ares

Length of output: 2987


🏁 Script executed:

# Search for the snapshot method implementation
rg -n "def snapshot" src/ares/containers/docker.py -A 15

Repository: withmartian/ares

Length of output: 765


🏁 Script executed:

# Check if label validation exists anywhere
rg -n "ares-go-explore" src/ares/containers/docker.py

Repository: withmartian/ares

Length of output: 167


🏁 Script executed:

# Check the full class structure for context
rg -n "class DockerContainer" src/ares/containers/docker.py -A 50

Repository: withmartian/ares

Length of output: 2559


Use the snapshot label as a delete guard.

snapshot() marks ARES-created images with ares-go-explore=true, but both delete paths ignore that marker and will remove any image ID they are given. That makes a bad or stale snapshot_id capable of deleting an unrelated local image. Validate the label before images.remove() and keep the current not-found idempotence on top.

🛡️ Suggested guard
+    def _assert_snapshot_image(self, snapshot_id: str) -> None:
+        image = self._client.images.get(snapshot_id)
+        labels = image.attrs.get("Config", {}).get("Labels") or {}
+        if labels.get("ares-go-explore") != "true":
+            raise ValueError(f"{snapshot_id} is not an ARES snapshot")
+
     async def delete_snapshot(self, snapshot_id: str) -> None:
         """Delete a Docker image created by snapshot()."""
         try:
+            await asyncio.to_thread(self._assert_snapshot_image, snapshot_id)
             await asyncio.to_thread(self._client.images.remove, snapshot_id)
             _LOGGER.info("Snapshot deleted: %s", snapshot_id)
         except docker.errors.ImageNotFound:
             _LOGGER.debug("Snapshot %s already deleted.", snapshot_id)
 
     def delete_snapshot_sync(self, snapshot_id: str) -> None:
         """Synchronous version for atexit cleanup."""
         with contextlib.suppress(docker.errors.ImageNotFound):
+            self._assert_snapshot_image(snapshot_id)
             self._client.images.remove(snapshot_id)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
tag = f"ares-snapshot-{uuid.uuid4().hex[:12]}"
image = await asyncio.to_thread(
self._container.commit,
repository="ares-go-explore",
tag=tag,
conf={"Labels": {"ares-go-explore": "true"}},
)
_LOGGER.info("Snapshot created: %s (tag: %s)", image.id, tag)
return image.id
async def delete_snapshot(self, snapshot_id: str) -> None:
"""Delete a Docker image created by snapshot()."""
try:
await asyncio.to_thread(self._client.images.remove, snapshot_id)
_LOGGER.info("Snapshot deleted: %s", snapshot_id)
except docker.errors.ImageNotFound:
_LOGGER.debug("Snapshot %s already deleted.", snapshot_id)
def delete_snapshot_sync(self, snapshot_id: str) -> None:
"""Synchronous version for atexit cleanup."""
with contextlib.suppress(docker.errors.ImageNotFound):
self._client.images.remove(snapshot_id)
tag = f"ares-snapshot-{uuid.uuid4().hex[:12]}"
image = await asyncio.to_thread(
self._container.commit,
repository="ares-go-explore",
tag=tag,
conf={"Labels": {"ares-go-explore": "true"}},
)
_LOGGER.info("Snapshot created: %s (tag: %s)", image.id, tag)
return image.id
def _assert_snapshot_image(self, snapshot_id: str) -> None:
image = self._client.images.get(snapshot_id)
labels = image.attrs.get("Config", {}).get("Labels") or {}
if labels.get("ares-go-explore") != "true":
raise ValueError(f"{snapshot_id} is not an ARES snapshot")
async def delete_snapshot(self, snapshot_id: str) -> None:
"""Delete a Docker image created by snapshot()."""
try:
await asyncio.to_thread(self._assert_snapshot_image, snapshot_id)
await asyncio.to_thread(self._client.images.remove, snapshot_id)
_LOGGER.info("Snapshot deleted: %s", snapshot_id)
except docker.errors.ImageNotFound:
_LOGGER.debug("Snapshot %s already deleted.", snapshot_id)
def delete_snapshot_sync(self, snapshot_id: str) -> None:
"""Synchronous version for atexit cleanup."""
with contextlib.suppress(docker.errors.ImageNotFound):
self._assert_snapshot_image(snapshot_id)
self._client.images.remove(snapshot_id)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ares/containers/docker.py` around lines 196 - 217, delete_snapshot and
delete_snapshot_sync remove any image ID given, so validate the ARES marker
label ("ares-go-explore" == "true") before calling images.remove to avoid
deleting unrelated images; in delete_snapshot use asyncio.to_thread to first
fetch the image metadata (e.g., self._client.images.get or inspect) and check
image.attrs['Config']['Labels'] (or image.labels) contains
"ares-go-explore":"true" and only then call images.remove (preserve the existing
ImageNotFound handling and _LOGGER.info/_LOGGER.debug behavior), and in
delete_snapshot_sync do the same synchronous inspect/get+label check before
self._client.images.remove, using
contextlib.suppress(docker.errors.ImageNotFound) to keep idempotence; if the
label is absent or not matching, log a warning/debug and skip removal.

@gowthamkishore3
Copy link
Copy Markdown

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 16, 2026

✅ Actions performed

Full review triggered.

@gowthamkishore3
Copy link
Copy Markdown

@coderabbitaidev full review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 17, 2026

@gowthamkishore3 I'll trigger a full review of PR #94 right away!

✅ Actions performed

Full review triggered.

@gowthamkishore3
Copy link
Copy Markdown

@coderabbitaidev full review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 17, 2026

@gowthamkishore3 I'll trigger a full review of PR #94 right away!

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
src/ares/containers/docker.py (1)

206-217: ⚠️ Potential issue | 🟠 Major

Still missing an ownership guard before image deletion.

snapshot() labels ARES-created images with ares-go-explore=true, but both delete paths still remove any image ID they're given. A stale or wrong snapshot_id can still delete an unrelated local image unless you inspect the label first and skip or reject non-ARES images.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ares/containers/docker.py` around lines 206 - 217, Both delete_snapshot
and delete_snapshot_sync must verify the image is owned by ARES before removing
it: call the Docker API to inspect/get the image (e.g., via
self._client.images.get(snapshot_id) or .get and check image.attrs.get("Config",
{}).get("Labels") or image.labels) and only proceed with
self._client.images.remove if the label ares-go-explore == "true"; if the image
is missing, preserve the existing ImageNotFound handling, and if the label is
absent or false, log a warning/debug and skip deletion (do this check in both
delete_snapshot and delete_snapshot_sync).
🧹 Nitpick comments (2)
src/ares/containers/docker.py (1)

219-229: Instantiate via cls in this classmethod.

Hardcoding DockerContainer(...) means SomeDockerSubclass.from_snapshot(...) would return the base type. Using cls(...) preserves subclass constructors and overrides.

♻️ Minimal change
-        return DockerContainer(image=snapshot_id, name=name, resources=resources, default_workdir=default_workdir)
+        return cls(image=snapshot_id, name=name, resources=resources, default_workdir=default_workdir)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ares/containers/docker.py` around lines 219 - 229, The classmethod
from_snapshot currently instantiates the concrete DockerContainer class
directly, which breaks subclassing; change the instantiation to use cls(...)
instead of DockerContainer(...) so that SomeDockerSubclass.from_snapshot(...)
returns an instance of the actual subclass; update the return expression in
from_snapshot to return cls(image=snapshot_id, name=name, resources=resources,
default_workdir=default_workdir).
src/ares/presets.py (1)

128-138: Surface preset-name collisions instead of silently dropping them.

Because preset_name is derived from _make_harbor_dataset_id(), two different Harbor specs can still collapse onto the same public name. Right now the later one disappears with no trace, which makes a missing preset hard to diagnose. Consider tracking the first (name, version) and logging when another spec hits the same preset_name.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ares/presets.py` around lines 128 - 138, The loop over
code_env.list_harbor_datasets() silently drops duplicates when preset_name
(built via _make_harbor_dataset_id and code_agent_id) collides because of the
seen set; change this to map preset_name -> first ds_spec identifier (e.g.,
(ds_spec.name, ds_spec.version)) instead of a plain seen set, and when
encountering a preset_name already present, emit a clear log or warning
(including the existing mapped (name,version) and the new ds_spec) rather than
silently continue; update references in this block (seen -> a dict, ds_spec,
_make_harbor_dataset_id, preset_name) and keep the original dedup behavior but
surface collisions for debugging.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/ares/containers/docker.py`:
- Around line 206-217: Both delete_snapshot and delete_snapshot_sync must verify
the image is owned by ARES before removing it: call the Docker API to
inspect/get the image (e.g., via self._client.images.get(snapshot_id) or .get
and check image.attrs.get("Config", {}).get("Labels") or image.labels) and only
proceed with self._client.images.remove if the label ares-go-explore == "true";
if the image is missing, preserve the existing ImageNotFound handling, and if
the label is absent or false, log a warning/debug and skip deletion (do this
check in both delete_snapshot and delete_snapshot_sync).

---

Nitpick comments:
In `@src/ares/containers/docker.py`:
- Around line 219-229: The classmethod from_snapshot currently instantiates the
concrete DockerContainer class directly, which breaks subclassing; change the
instantiation to use cls(...) instead of DockerContainer(...) so that
SomeDockerSubclass.from_snapshot(...) returns an instance of the actual
subclass; update the return expression in from_snapshot to return
cls(image=snapshot_id, name=name, resources=resources,
default_workdir=default_workdir).

In `@src/ares/presets.py`:
- Around line 128-138: The loop over code_env.list_harbor_datasets() silently
drops duplicates when preset_name (built via _make_harbor_dataset_id and
code_agent_id) collides because of the seen set; change this to map preset_name
-> first ds_spec identifier (e.g., (ds_spec.name, ds_spec.version)) instead of a
plain seen set, and when encountering a preset_name already present, emit a
clear log or warning (including the existing mapped (name,version) and the new
ds_spec) rather than silently continue; update references in this block (seen ->
a dict, ds_spec, _make_harbor_dataset_id, preset_name) and keep the original
dedup behavior but surface collisions for debugging.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 52c9edd3-5569-4e90-b8bd-2c25edfcf20e

📥 Commits

Reviewing files that changed from the base of the PR and between c804aa2 and 49b5f74.

📒 Files selected for processing (5)
  • src/ares/containers/__init__.py
  • src/ares/containers/containers.py
  • src/ares/containers/docker.py
  • src/ares/containers/docker_snapshot_test.py
  • src/ares/presets.py

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.

2 participants