github CodesWhat/drydock v1.5.0-rc.26

pre-release5 hours ago

v1.5.0-rc.26

Full Changelog: v1.5.0-rc.25...v1.5.0-rc.26

[1.5.0-rc.26] — 2026-05-22

Fixed

  • Image reference construction — unanchored /v2 strip could silently corrupt references when the image name contained a /v2 path segment. Registry.getImageFullName and the controller-mode fallback in resolveContainerImageFullName both applied .replace(/\/v2/, '') to the fully concatenated registryUrl/imageName:tag string. Because the regex was unanchored and non-global, if the image name contained a /v2 segment (e.g. library/v2/tool) the strip would remove it from the image name rather than the registry URL — producing a silently wrong reference handed to Trivy. The fix extracts a shared pure helper buildImageReference (app/registries/image-reference.ts) that cleans the registry URL before concatenation using anchored regexes (^https?:\/\/ and /v2\/?$) so the URL scheme and trailing /v2 API path are removed without touching anything in the image name. Both Registry.getImageFullName and the fallback branch of resolveContainerImageFullName now delegate to this helper, eliminating the duplicate logic.

  • #386 — Agents intermittently showing 0 running containers in the controller UI — a second recurrence the rc.25 fix did not close. The rc.25 fix suppressed the authoritative watcher snapshot whenever container enumeration failed or per-container enrichment errors dropped containers, but the recurrence reported on rc.25 is a different failure mode: a cold-start race between the controller's handshake and the agent's first watch cycle. When the controller's AgentClient (re)connects to an agent's SSE stream it handshakes immediately via GET /api/containers; if the agent's watchatstart cron has not yet finished its first run, the agent's in-memory store is still empty and the handshake legitimately receives 0 containers (the agent log shows Handshake successful. Received 0 containers. ~5 s before Cron finished (4 containers watched, 0 errors)). The handshake then fires emitAgentConnected, the UI re-fetches /api/v1/agents, and the agent's running-container count renders 0. When the agent's cron completes moments later it pushes a dd:watcher-snapshot, and AgentClient.handleWatcherSnapshotEvent ingests the four containers into the controller store correctly — but nothing told the UI to refresh, because AgentsView only re-fetches the agent summary on agent-status-changed / connected / resync-required events, not on container-added / container-updated. The stale 0 therefore persisted until an unrelated reconnect event (such as an agent restart) fired. The fix adds a dedicated AgentStatsChanged event: app/event/index.ts gains emitAgentStatsChanged / registerAgentStatsChanged (mirroring the existing AgentConnected pair); AgentClient.handleWatcherSnapshotEvent now emits emitAgentStatsChanged({ agentName }) after every completed watcher snapshot; app/api/sse.ts broadcasts it to UI SSE clients as dd:agent-stats-changed; and ui/src/stores/eventStream.ts maps that to the existing agent-status-changed bus event. A completed agent watch cycle therefore always refreshes the controller's agent-summary count, even when the handshake raced ahead of the agent's first cron.

  • #342 — A container is no longer shown as "update available" with a blank target version after a transient registry error. hasRawUpdate in app/model/container.ts compared transformTag(image.tag.value) against transformTag(result.tag) without guarding an undefined result.tag. When a registry scan failed mid-flight (for example a Docker Hub or GHCR 429) and left a container result present but its tag unset, transformTag(undefined) returned undefined, the localTag !== remoteTag comparison evaluated true, and the container was flagged updateAvailable with an unknown update kind — which the UI renders as an update with no target version (the reporter saw this on immich_redis). hasRawUpdate now performs the tag comparison only when both image.tag.value and result.tag are defined, matching the existing guard in getRawTagUpdate. Digest-only updates are unaffected: a container with an undefined result.tag but a genuine digest change still reports the digest update.

  • #386 follow-through — the controller's agent-summary container count now also refreshes on docker-event-driven container changes, not only completed cron cycles. The initial #386 fix emitted emitAgentStatsChanged from AgentClient.handleWatcherSnapshotEvent, the cron-watch path. An agent also ingests individual container add/remove/update events from the Docker event stream between cron cycles (handleContainerChangeEvent, handleContainerRemovedEvent), via the controller-initiated watch() path, and via the per-container controller-initiated watchContainer() path — none of which emitted the stats-changed signal, so a container started or stopped on an agent host could leave the AgentsView running-container count stale until the next 6-hourly cron. All four paths now emit emitAgentStatsChanged after mutating the controller store, keeping the count current in real time.

  • #342 — GitHub release-notes lookups now survive GitHub's secondary rate limit instead of giving up on the first burst. Drydock authenticates its api.github.com release-notes requests by reusing the configured GHCR token, but a watch cycle still fans out a lookup for every watched container at once and trips GitHub's secondary rate limit — a 403 GitHub returns to authenticated callers who burst too many requests. The shared retry helper (app/registries/http-retry.ts) only retried 429/503, so the secondary-limit 403 was never retried: the provider logged GitHub release notes lookup is rate-limited and returned nothing. withRetry gains two optional, opt-in hooks — retryPredicate (retry a status outside retryableStatuses) and retryDelayMs (per-attempt delay override) — leaving every existing caller unchanged. GithubProvider classifies a 403 as a secondary rate limit only when it carries a retry-after header or x-ratelimit-remaining: 0, retries those (honouring retry-after / x-ratelimit-reset for the delay), and leaves a genuine 403 authorization failure failing fast as before. Once retries are exhausted the provider arms a short module-level cooldown — driven by GitHub's own retry hint, floored at the 60 s default so a retry-after: 0 hint cannot produce an already-expired cooldown — during which further release-notes lookups are skipped, so a single cron cycle no longer hammers an already-tripped limit container after container. The rate-limit warning now also records whether the request was authenticated.

  • #342 — the registry-error tooltip on the Containers view now names the registry that failed. When a registry tag lookup errors (for example a 429 rate limit) the container shows a registry-error badge whose tooltip previously rendered only the raw transport message — Registry error: Request failed with status code 429 — with no indication of which registry was queried. registryErrorTooltip in ui/src/views/ContainersView.vue now derives the registry hostname from the container's registryUrl and renders it through a new registryError.detailWithRegistry i18n string ({registryHost} — {error}), e.g. ghcr.io — Request failed with status code 429. Containers whose registryUrl is absent or unparseable fall back to the original message unchanged.

Don't miss a new drydock release

NewReleases is sending notifications on new releases.