Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 22 additions & 4 deletions openhands-agent-server/openhands/agent_server/docker/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,24 +226,42 @@ def _base_slug(image: str, max_len: int = 64) -> str:

# Parse components from the slug form
if "_tag_" in base_slug:
left, tag = base_slug.split("_tag_", 1)
# Ports also become "_tag_" (e.g., "ghcr.io:443/repo:tag"
# -> "ghcr.io_tag_443_s_repo_tag_tag"). Use rsplit so we split on the real
# image tag (the last _tag_), not the port.
left, tag = base_slug.rsplit("_tag_", 1)
else:
left, tag = base_slug, ""

parts = left.split("_s_") if left else []
repo = parts[-1] if parts else left # last path segment is the repo

# Reconstruct a compact, identifiable core: "<repo>[_tag_<tag>]"
ident = repo + (f"_tag_{tag}" if tag else "")
tag_piece = f"_tag_{tag}" if tag else ""
ident = repo + tag_piece

# Fit within budget, reserving space for the digest suffix
visible_budget = max_len - len(suffix)
assert visible_budget > 0, (
f"max_len too small to fit digest suffix with length {len(suffix)}"
)

kept = ident[:visible_budget]
return kept + suffix
if len(ident) > visible_budget:
if tag_piece and len(tag_piece) < visible_budget:
# Full tag fits: trim repo to make room
ident = repo[: visible_budget - len(tag_piece)] + tag_piece
else:
# Either no tag or tag too long: truncate from the start
ident = (tag_piece or ident)[:visible_budget]

truncated = ident + suffix
logger.debug(
"[base-slug] Truncated base image '%s' to slug '%s' (max_len=%d)",
image,
truncated,
max_len,
)
return truncated


def _git_info() -> tuple[str, str]:
Expand Down
33 changes: 33 additions & 0 deletions tests/agent_server/test_docker_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,39 @@ def test_base_slug_truncation_no_tag():
assert "very-long-repository-name" in result


def test_base_slug_preserves_latest_tag_suffix():
"""Ensure tag_latest suffix is not mangled when truncating long slugs."""
from openhands.agent_server.docker.build import _base_slug

image = (
"docker.io/swebench/sweb.eval.x86_64.astropy_1776_astropy-8872:"
"tag_latest-0a797356ebce"
)

result = _base_slug(image, max_len=64)

assert len(result) <= 64
assert "tag_latest" in result
# Keep cache keys stable with digest suffix
assert result[-13:-12] == "-"
assert all(c in "0123456789abcdef" for c in result[-12:])


def test_base_slug_preserves_tag_with_registry_port():
"""Handle registries with ports without losing the tag segment."""
from openhands.agent_server.docker.build import _base_slug

image = (
"localhost:5001/swebench/sweb.eval.x86_64.astropy_1776_astropy-8872:"
"tag_latest-0a797356ebce"
)

result = _base_slug(image, max_len=64)

assert len(result) <= 64
assert "tag_latest" in result


def test_base_slug_custom_max_len():
"""Test base_slug with custom max_len parameter."""
from openhands.agent_server.docker.build import _base_slug
Expand Down
Loading