Skip to content

Keep waiting for build image while registry import is in flight#310

Open
hiroTamada wants to merge 1 commit into
mainfrom
hypeship/fix-build-image-import-race
Open

Keep waiting for build image while registry import is in flight#310
hiroTamada wants to merge 1 commit into
mainfrom
hypeship/fix-build-image-import-race

Conversation

@hiroTamada

@hiroTamada hiroTamada commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes spurious image conversion failed: get image: image not found build failures under concurrent deploy bursts.

The registry imports pushed images asynchronously: after the manifest PUT returns 201, triggerConversion copies blobs into the OCI layout and only then calls ImportLocalImage, which creates the image record. Under a burst of concurrent builds this import is disk-bound and can take well over 30 seconds — longer than the fixed maxWaitForExist existence deadline inside images.WaitForReady. The build manager then fails the build even though the push succeeded and the image reaches ready seconds later. Observed in production: a build declared failed at its 30s deadline whose image record went ready 5 seconds after; ~15% of deploys failed this way during bursts, and retries succeeded.

Since waitForImageReady only runs after the builder has successfully pushed (buildctl pushes synchronously before reporting build_result), a not-found at this point means the import is still in flight — not that the push failed. This change makes the build flow keep retrying on images.ErrNotFound until the build context (policy.TimeoutSeconds) expires, instead of giving up on the first existence timeout. Other errors (conversion failure, context cancellation) still fail immediately, and a genuinely-missing image now fails at the build timeout with the same error message.

The 30s default inside images.WaitForReady is unchanged, so the instance-start pull path keeps its existing behavior.

Changes

  • lib/builds/manager.go: retry WaitForReady on ErrNotFound in waitForImageReady, bounded by the build context
  • lib/builds/manager_test.go: mock WaitForReady now returns not-found for missing images, mirroring the real manager
  • lib/builds/race_test.go: regression tests for late import and never-imported cases

Testing

  • go test ./lib/builds/ passes locally (full package, including the two new regression tests)
  • Not exercised against a live hypeman host

🤖 Generated with Claude Code


Note

Medium Risk
Changes post-build completion timing and failure semantics for the build→image handoff; bounded by existing build timeout and limited to ErrNotFound retries.

Overview
Fixes false failed builds when the push succeeded but the image record is not in the image manager yet because registry import is still running.

waitForImageReady no longer treats a single images.ErrNotFound from WaitForReady as terminal. After a successful builder push, not-found is interpreted as import in flight; the build manager loops with a 1s imageImportRetryInterval until the image is ready or the build context (policy.TimeoutSeconds) ends. Conversion failures and other non–not-found errors still fail immediately.

Test support: the mock WaitForReady returns not-found for missing images (matching production), plus regression tests for late import and never-imported timeout.

Reviewed by Cursor Bugbot for commit 24d9b52. Bugbot is set up for automated code reviews on this repo. Configure here.

The registry imports pushed images asynchronously (OCI layout copy +
conversion enqueue), and under concurrent build bursts that import can
take longer than the image manager's 30s existence deadline in
WaitForReady. The build then fails with "image conversion failed: get
image: image not found" even though the push succeeded and the image
converts fine seconds later.

Since waitForImageReady only runs after the builder has successfully
pushed, treat not-found as import-still-in-flight and keep retrying
until the build context expires instead of failing on the first
existence timeout.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@hiroTamada
hiroTamada marked this pull request as ready for review July 20, 2026 18:36

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 24d9b52. Configure here.

Comment thread lib/builds/race_test.go
err := mgr.waitForImageReady(ctx, "builds/test-build-never-imported")
require.Error(t, err)
assert.ErrorIs(t, err, images.ErrNotFound)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Flaky never-imported timeout test

Medium Severity

TestWaitForImageReady_NotFoundUntilContextExpires expects images.ErrNotFound when the context expires, but that only happens if the mock returns not-found before noticing cancellation. If ctx is already done when the next WaitForReady starts, the mock returns context.DeadlineExceeded and the outer loop exits immediately, so the assertion flakes. The real WaitForReady also returns ctx.Err() during its existence poll, so this test does not match production timeout behavior.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 24d9b52. Configure here.

Comment thread lib/builds/manager.go
if !errors.Is(err, images.ErrNotFound) {
return err
}
m.logger.Info("build image not yet imported by registry, retrying", "image_ref", imageRef)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Retry matches conversion not-found errors

Low Severity

waitForImageReady retries on any error wrapping images.ErrNotFound, but WaitForReady can return that sentinel from a real conversion failure via the status-event path (image conversion failed with %w). Those failures are then misclassified as “import still in flight,” causing a spurious retry and misleading log before the next attempt sees failed status and exits.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 24d9b52. Configure here.

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.

1 participant