Skip to content

Our changes on top of upstream buildkit (review view)#17

Open
kmannislands wants to merge 296 commits into
moby-merge-basefrom
merge-in-buildkit-all-at-once
Open

Our changes on top of upstream buildkit (review view)#17
kmannislands wants to merge 296 commits into
moby-merge-basefrom
merge-in-buildkit-all-at-once

Conversation

@kmannislands

Copy link
Copy Markdown

Same branch as #14, but based on the moby merge point (fa19659) so the diff shows only EarthBuild's changes, not all of upstream.

This is a review-only view — the actual merge to main is #14. Do not merge this one.

The base branch moby-merge-base is a throwaway pointer to the moby commit we merged in; keep it around while this PR is open.

Alex Couture-Beil and others added 30 commits January 22, 2021 11:01
- the github.com/moby/buildkit/session/localhost package is used by the
client buildkit code, which must compile on both linux and darwin;
however the session/localhost package was pulling in the executor
package which is only meant to be compiled on linux and not darwin (due
to it referncing linux-specific syscall consts)

Signed-off-by: Alex Couture-Beil <alex@earthly.dev>
- Allows llb.Run command to request local files
- Simplifies how we signal llb.Run to execute commands locally

Signed-off-by: Alex Couture-Beil <alex@earthly.dev>
- darwin builds pull in session/localhost; however pulling in snapshot
doesn't work on darwin, so just create a small interface for the single
function we need.

Signed-off-by: Alex Couture-Beil <alex@earthly.dev>
Signed-off-by: Alex Couture-Beil <alex@earthly.dev>
Signed-off-by: Alex Couture-Beil <alex@earthly.dev>
- when workdir is "" that means the current directory
- additionaly removes some left over debugging code that was
  issuing "ls -la"

Signed-off-by: Alex Couture-Beil <alex@earthly.dev>
Signed-off-by: Vlad A. Ionescu <vladaionescu@users.noreply.github.com>
* Limit parallelism of the worker.

* Make parallelism configurable via env var.

Co-authored-by: Vlad A. Ionescu <vladaionescu@users.noreply.github.com>
Revert "Increase some timeouts to improve stability with high parallelism."

This reverts commit 9023ed8.

Revert "Increase some timeouts to improve stability with high parallelism."

This reverts commit 81d7726.
Signed-off-by: Alex Couture-Beil <alex@earthly.dev>
- on the final message that is sent to the localhost provider client,
buildkit would just close the stream and return without waiting for the
client to return a final confirmation that the entire file was written
to disk.

Signed-off-by: Alex Couture-Beil <alex@earthly.dev>
* contenthash: include basename in content checksum for wildcards

While we generally ignore the basename in this layer, for wildcards
there in no other place to add the basename to the checksum as they
can not be resolved earlier. Before the basename that was in the
checksum was the wildcard itself, so if the wildcard remained same,
content remained same but the file where wildcard pointed to was
renamed, the cache was not invalidated.

Unfortunately, this change breaks cache for all copy commands that
use a wildcard.

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>

* Fix missing mounts in execOp cache map

A bug in cloneExecOp prevented mounts from being included in the digest
computed for the execOp cache map. This could lead to an exec being
wrongly cached when a different mount was used for a subsequent
execution.

Repro case:
https://gist.github.com/aaronlehmann/cfeaefc028df8127fb85b9b5f9125f2d

In this example, pass2 should generate an empty diff because the /from
and /to mounts are the same busybox image. But before the fix, it uses
the cached result from pass1 (with different mounts) instead.

Signed-off-by: Aaron Lehmann <alehmann@netflix.com>

* Add regression test for run cache not considering mounts

Signed-off-by: Aaron Lehmann <alehmann@netflix.com>

* Add hack to preserve Dockerfile RUN cache compatibility after mount cache bugfix

Signed-off-by: Aaron Lehmann <alehmann@netflix.com>

* Fix potential crash on nil value

Signed-off-by: Pierre Fenoll <pierrefenoll@gmail.com>

* Dockerfile: update containerd to v1.5.0-rc.2 / v1.4.4

Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>

* go.mod: containerd v1.5.0-rc.2

Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>

* Remove potentially expired tokens.

Some registries (notably Quay) issue tokens that expire without providing an `expires in` value in the authorization payload. Therefore, if a token produces a 401, we should remove it and re-fetch.

Signed-off-by: Corey Larson <corey@earthly.dev>

* Add "ALL_PROXY" to list of default args / proxy env-vars

Relates to https://github.com/linuxkit/linuxkit/blob/a82fff637781877fc0d88c0f6e514fcd24699c46/docs/packages.md#proxies

> (..) the first four of these are the standard built-in build-arg options
> available for `docker build`
> (..) The last, `all_proxy`, is a standard var used for socks proxying. Since
> it is not built into `docker build`, if you want to use it, you will need to
> add the following line to the dockerfile:
>
>     ARG all_proxy

Given the we support all other commonly known proxy env-vars by default, it makes
sense to add this one as well.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>

* Redact credentials from URLs before returning errors

this is to prevent errors such as

    failed to fetch remote https://user:password@github.com/user/private-repo-failure.git: exit status 128

from leaking the password; now it will be displayed like:

    failed to fetch remote https://user:xxxxx@non-existant-host/user/private-repo.git: exit status 128

Signed-off-by: Alex Couture-Beil <alex@earthly.dev>

* regenerate proto files

Co-authored-by: Tonis Tiigi <tonistiigi@gmail.com>
Co-authored-by: Aaron Lehmann <alehmann@netflix.com>
Co-authored-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
Co-authored-by: Pierre Fenoll <pierrefenoll@gmail.com>
Co-authored-by: Corey Larson <corey@earthly.dev>
Co-authored-by: Sebastiaan van Stijn <github@gone.nl>
…rthly#39)

* Start up a registry on buildkit startup.

* Add forgotten files.

* Some debug output to track how registry storage driver and multiprovider work.

* Working POC. Needs follow-up work.

* Support for docker pull ping and reply. Client-side integration.

* Refactor the Earthly exporter to use less maps.

* Fix dir export bug. Slightly improved exporting output.

* Configurable registry port. Don't start registry unless env var is set.

Co-authored-by: Vlad A. Ionescu <vladaionescu@users.noreply.github.com>
Co-authored-by: Vlad A. Ionescu <vladaionescu@users.noreply.github.com>
* Increase initial window size for grpc communication.

* Undo an experiment.

Co-authored-by: Vlad A. Ionescu <vladaionescu@users.noreply.github.com>
gilescope added 15 commits April 9, 2026 04:22
The vendored fsutil's wire.proto uses bare import "stat.proto" which
needs the fsutil types directory on protoc's include path.

Signed-off-by: Giles Cope <gilescope@gmail.com>
After the upstream merge added CDI support, the earthly-specific
parameters (hooks, sampleFrequency, cdiManager) were passed in the
wrong order at the call site.

Signed-off-by: Giles Cope <gilescope@gmail.com>
- Fix Env field incorrectly set to hook.Args instead of hook.Env
- Suppress staticcheck SA1019 for intentional legacy prestart support

Signed-off-by: Giles Cope <gilescope@gmail.com>
Resolve 47 lint issues introduced by upstream's stricter lint config:
- errorlint: use errors.Is/errors.As instead of == and type assertions
- forbidigo: use context.WithCancelCause/WithTimeoutCause/Cause
- gocritic: fix comment formatting (space after //)
- gofmt: reformat 4 files
- staticcheck: fix deprecated imports, simplify selectors, nolint pkg names
- noctx: use DialContext/Listen with context
- unused: remove dead code

Signed-off-by: Giles Cope <gilescope@gmail.com>
- Replace interface{} with any
- Use strings.CutPrefix instead of HasPrefix+TrimPrefix
- Use maps.Copy instead of manual k/v loops
- Modernize for loops to range over int
- Remove unnecessary variable copy
- Fix comment formatting (space after //)

Signed-off-by: Giles Cope <gilescope@gmail.com>
Sort maps import alphabetically among stdlib imports.

Signed-off-by: Giles Cope <gilescope@gmail.com>
Prefix unused function parameters with _ to satisfy the gopls
unusedparams analyzer.

Signed-off-by: Giles Cope <gilescope@gmail.com>
Earthly's base64 encoding of git stderr prevented upstream code in
source/git/source.go from detecting "would clobber existing tag" and
"refname conflict" errors via strings.Contains. Include raw redacted
stderr alongside the base64 so pattern matching works.

Also update TestGitCLIConfigEnv to match earthly's intentional behavior
of not isolating git config (HOME is always passed through).

Signed-off-by: Giles Cope <gilescope@gmail.com>
go mod tidy removed unused direct deps (containerd v1, docker/docker,
gogo/googleapis, go-fuzz-headers) left over from the upstream merge.
go mod vendor then cleaned up 149 files of unused vendored code.

Signed-off-by: Giles Cope <gilescope@gmail.com>
- Remove gogoproto.stdtime annotations from control.proto (earthly-
  specific SessionHistory fields) since gogo protoc is no longer used
- Fix vendored fsutil wire.proto bare import to use fully-qualified path
- Exclude earthly-specific protos (localhost, pullping, socketforward,
  registry, gateway) from protoc generation as they use old gogo-style
  codegen incompatible with modern protoc-gen-go
- Add M-flag overrides for fsutil types proto go_package paths
- Remove hand-written control_earthly.pb.go (now generated by protoc)
- Update control.go to use timestamppb.New() for History timestamps

Signed-off-by: Giles Cope <gilescope@gmail.com>
Signed-off-by: Giles Cope <gilescope@gmail.com>
- Fix copylocks warning in client_test.go: types.Stat now contains
  a mutex via protoimpl.MessageState, use pointers instead of copies
- Re-vendor after go mod tidy to sync vendor with updated fsutil

Signed-off-by: Giles Cope <gilescope@gmail.com>
Signed-off-by: Giles Cope <gilescope@gmail.com>
Earthly's multi-BUILD pattern legitimately produces multiple refs
without platform mapping.  The upstream verifier added in buildkit
treats this as a hard error; downgrade to a warning so earthly builds
continue to work.

Signed-off-by: Giles Cope <gilescope@gmail.com>
Upstream commit 7aaa797 caches gateway mounts for performance, but
this causes stale reads when a persistent cache mount is modified by
a RUN and then read by a subsequent SAVE ARTIFACT within the same
target. Disable the cache lookup so each operation gets a fresh mount.

Signed-off-by: Giles Cope <gilescope@gmail.com>
@socket-security

socket-security Bot commented Jun 17, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedgolang/​github.com/​docker/​distribution@​v2.8.3+incompatible73100100100100
Addedgolang/​github.com/​grpc-ecosystem/​go-grpc-middleware@​v1.4.0100100100100100

View full report

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces several Earthly-specific enhancements to Buildkit, including an embedded registry proxy, worker GC analytics, configurable session timeouts, and localhost execution capabilities. The review feedback highlights several critical issues: potential data loss and deadlocks in the registry proxy, severe performance overhead on the scheduler's hot path due to eager string formatting, a potential out-of-bounds panic in the client dialer, and file corruption risks in localhost file transfers. Additionally, there are concurrency bugs in image registration cleanup, session history tracking issues, and unstable API response ordering in the session history endpoint.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +56 to +72
func (s *StreamRW) Read(p []byte) (int, error) {
l := 0
if len(s.last) > 0 {
l = copy(p, s.last)
}

msg, err := s.stream.Recv()
if err != nil {
return 0, err
}

s.last = msg.GetData()
n := copy(p, s.last)
s.last = s.last[n:]

return n + l, nil
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

The current Read implementation does not shrink s.last after copying its contents to p. If len(s.last) > len(p), the uncopied bytes are completely lost when s.stream.Recv() is called and overwrites s.last. Additionally, if s.last already has buffered data, the function should satisfy the read request immediately without blocking on s.stream.Recv().

Suggested change
func (s *StreamRW) Read(p []byte) (int, error) {
l := 0
if len(s.last) > 0 {
l = copy(p, s.last)
}
msg, err := s.stream.Recv()
if err != nil {
return 0, err
}
s.last = msg.GetData()
n := copy(p, s.last)
s.last = s.last[n:]
return n + l, nil
}
func (s *StreamRW) Read(p []byte) (int, error) {
if len(s.last) > 0 {
n := copy(p, s.last)
s.last = s.last[n:]
return n, nil
}
msg, err := s.stream.Recv()
if err != nil {
return 0, err
}
s.last = msg.GetData()
n := copy(p, s.last)
s.last = s.last[n:]
return n, nil
}

Comment thread solver/scheduler.go
e.hasActiveOutgoing = true
}
}
helpMe = append(helpMe, fmt.Sprintf("hasActiveOutgoing %v", e.hasActiveOutgoing))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Eagerly appending to helpMe on the hot path should be removed to avoid performance overhead.

Suggested change
helpMe = append(helpMe, fmt.Sprintf("hasActiveOutgoing %v", e.hasActiveOutgoing))
// earthly-specific: removed eager helpMe append

Comment thread client/client.go
Comment on lines +181 to +186
if useDefaultDialer {
split := strings.Split(address, "://")
if len(split) > 0 {
address = split[1]
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

If address does not contain ://, strings.Split(address, "://") returns a slice of length 1. Accessing split[1] without checking len(split) > 1 will cause an index out of bounds panic.

Suggested change
if useDefaultDialer {
split := strings.Split(address, "://")
if len(split) > 0 {
address = split[1]
}
}
if useDefaultDialer {
split := strings.Split(address, "://")
if len(split) > 1 {
address = split[1]
}
}

// ...???XXX?????? -> ...???000000000 -> ...???000000XXX -> ...???000XXXXXX -> ...???XXXXXXXXX
mode = (mode ^ (mode & 0777)) | umode | (umode << 3) | (umode << 6)

f, err := os.OpenFile(dest, os.O_CREATE|os.O_WRONLY, os.FileMode(mode))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Opening the destination file with os.O_CREATE|os.O_WRONLY without os.O_TRUNC will cause data corruption if the file already exists and is larger than the new file being copied, as the leftover bytes of the old file will remain at the end.

Suggested change
f, err := os.OpenFile(dest, os.O_CREATE|os.O_WRONLY, os.FileMode(mode))
f, err := os.OpenFile(dest, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(mode))

Comment thread solver/scheduler.go
Comment on lines +104 to +108
helpMe := []string{"v1"} // earthly specific

helpMe = append(helpMe, fmt.Sprintf("%s: %+v", e.edge.Vertex.Name(), e))

helpMe = append(helpMe, fmt.Sprintf("%d %d %d %d", len(s.incoming), len(s.outgoing), len(s.incoming[e]), len(s.outgoing[e])))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Eagerly constructing helpMe with fmt.Sprintf and formatting the entire edge struct (%+v) on every single dispatch call introduces massive CPU overhead and GC pressure on the scheduler's hot path. This should be lazily constructed only when the validation error actually occurs.

Suggested change
helpMe := []string{"v1"} // earthly specific
helpMe = append(helpMe, fmt.Sprintf("%s: %+v", e.edge.Vertex.Name(), e))
helpMe = append(helpMe, fmt.Sprintf("%d %d %d %d", len(s.incoming), len(s.outgoing), len(s.incoming[e]), len(s.outgoing[e])))
// earthly-specific helpMe is lazily constructed only on error to avoid overhead

Comment thread solver/scheduler.go
Comment on lines 193 to 210
e.keysDidChange = false
}

helpMe = append(helpMe, fmt.Sprintf("in: %d out: %d", len(openIncoming), len(openOutgoing)))

// validation to avoid deadlocks/resource leaks:
// TODO: if these start showing up in error reports they can be changed
// to error the edge instead. They can only appear from algorithm bugs in
// unpark(), not for any external input.
if len(openIncoming) > 0 && len(openOutgoing) == 0 {
bklog.G(context.TODO()).Errorf("return leaving incoming open help me: %s\n", strings.Join(helpMe, ";"))
e.markFailed(pf, errors.New("buildkit scheduler error: return leaving incoming open. Please report this with BUILDKIT_SCHEDULER_DEBUG=1"))
}
if len(openIncoming) == 0 && len(openOutgoing) > 0 {
bklog.G(context.TODO()).Errorf("return leaving outgoing open help me: %s\n", strings.Join(helpMe, ";"))
e.markFailed(pf, errors.New("buildkit scheduler error: return leaving outgoing open. Please report this with BUILDKIT_SCHEDULER_DEBUG=1"))
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Construct the helpMe diagnostic slice lazily only when the scheduler validation actually fails.

		e.keysDidChange = false
	}

	// validation to avoid deadlocks/resource leaks:
	// TODO: if these start showing up in error reports they can be changed
	// to error the edge instead. They can only appear from algorithm bugs in
	// unpark(), not for any external input.
	if len(openIncoming) > 0 && len(openOutgoing) == 0 {
		helpMe := []string{"v1"}
		helpMe = append(helpMe, fmt.Sprintf("%s: %+v", e.edge.Vertex.Name(), e))
		helpMe = append(helpMe, fmt.Sprintf("%d %d %d %d", len(s.incoming), len(s.outgoing), len(s.incoming[e]), len(s.outgoing[e])))
		helpMe = append(helpMe, fmt.Sprintf("hasActiveOutgoing %v", e.hasActiveOutgoing))
		helpMe = append(helpMe, fmt.Sprintf("make openIncoming %d", len(inc)))
		for _, r := range s.incoming[e] {
			helpMe = append(helpMe, fmt.Sprintf("%+v", r.Sender.Status()))
		}
		helpMe = append(helpMe, fmt.Sprintf("in: %d out: %d", len(openIncoming), len(openOutgoing)))
		bklog.G(context.TODO()).Errorf("return leaving incoming open help me: %s\n", strings.Join(helpMe, ";"))
		e.markFailed(pf, errors.New("buildkit scheduler error: return leaving incoming open. Please report this with BUILDKIT_SCHEDULER_DEBUG=1"))
	}
	if len(openIncoming) == 0 && len(openOutgoing) > 0 {
		helpMe := []string{"v1"}
		helpMe = append(helpMe, fmt.Sprintf("%s: %+v", e.edge.Vertex.Name(), e))
		helpMe = append(helpMe, fmt.Sprintf("%d %d %d %d", len(s.incoming), len(s.outgoing), len(s.incoming[e]), len(s.outgoing[e])))
		helpMe = append(helpMe, fmt.Sprintf("hasActiveOutgoing %v", e.hasActiveOutgoing))
		helpMe = append(helpMe, fmt.Sprintf("make openOutgoing %d", len(out)))
		for _, r := range s.outgoing[e] {
			helpMe = append(helpMe, fmt.Sprintf("%+v", r.Receiver.Status()))
		}
		helpMe = append(helpMe, fmt.Sprintf("in: %d out: %d", len(openIncoming), len(openOutgoing)))
		bklog.G(context.TODO()).Errorf("return leaving outgoing open help me: %s\n", strings.Join(helpMe, ";"))
		e.markFailed(pf, errors.New("buildkit scheduler error: return leaving outgoing open. Please report this with BUILDKIT_SCHEDULER_DEBUG=1"))
	}

Comment on lines +133 to +138
go func() {
<-ctx.Done()
mmp.mu.Lock()
defer mmp.mu.Unlock()
mmp.maybeDelete(imgName)
}()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The cleanup goroutine deletes the image by name (imgName) when the context is canceled. However, if a new image with the same name is registered under a different context before the old context is canceled, the old context's cancellation will incorrectly delete the new image from the provider. We should check that the image currently in the map is the same instance before deleting it.

	go func() {
		<-ctx.Done()
		mmp.mu.Lock()
		defer mmp.mu.Unlock()
		if mmp.imgs[imgName] == imgData {
			mmp.maybeDelete(imgName)
		}
	}()

Comment thread session/manager.go
Comment on lines +144 to +148
for id, history := range sm.history {
if time.Since(history.Start) > sm.historyDuration {
delete(sm.history, 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.

medium

If BUILDKIT_SESSION_HISTORY_DURATION is not configured, sm.historyDuration defaults to 0. This causes time.Since(history.Start) > sm.historyDuration to always evaluate to true, wiping out the entire session history immediately when any session ends. We should only perform the cleanup if historyDuration is greater than zero.

	if sm.historyDuration > 0 {
		for id, history := range sm.history {
			if time.Since(history.Start) > sm.historyDuration {
				delete(sm.history, id)
			}
		}
	}

Comment on lines +529 to +545
if img.expSrc.Ref != nil { // This is a copy and paste of the above code
if len(img.platforms) != 0 {
return nil, nil, nil, errors.New("img.platforms should not be set when a single ref is used")
}

var ref cache.ImmutableRef
var p exptypes.Platform
if len(img.platforms) > 0 {
p = img.platforms[0]
if r, ok := img.expSrc.FindRef(p.ID); ok {
ref = r
} else {
return nil, nil, nil, errors.Errorf("img.expSrc.FindRef failed on %s", p.ID)
}
} else {
ref = img.expSrc.Ref
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Because of the check if len(img.platforms) != 0 at line 530, len(img.platforms) is guaranteed to be 0 in the else branch. Therefore, the condition if len(img.platforms) > 0 at line 536 is dead code and can never be executed. We can simplify this block by directly assigning ref = img.expSrc.Ref.

Suggested change
if img.expSrc.Ref != nil { // This is a copy and paste of the above code
if len(img.platforms) != 0 {
return nil, nil, nil, errors.New("img.platforms should not be set when a single ref is used")
}
var ref cache.ImmutableRef
var p exptypes.Platform
if len(img.platforms) > 0 {
p = img.platforms[0]
if r, ok := img.expSrc.FindRef(p.ID); ok {
ref = r
} else {
return nil, nil, nil, errors.Errorf("img.expSrc.FindRef failed on %s", p.ID)
}
} else {
ref = img.expSrc.Ref
}
if img.expSrc.Ref != nil {
if len(img.platforms) != 0 {
return nil, nil, nil, errors.New("img.platforms should not be set when a single ref is used")
}
ref := img.expSrc.Ref

Comment thread control/control.go
Comment on lines +688 to +699
func (c *Controller) SessionHistory(ctx context.Context, r *controlapi.SessionHistoryRequest) (*controlapi.SessionHistoryResponse, error) {
history := c.opt.SessionManager.GetSessionHistory()
var resp []*controlapi.SessionHistoryResponse_History
for id, h := range history {
resp = append(resp, &controlapi.SessionHistoryResponse_History{
SessionID: id,
Start: timestamppb.New(h.Start),
End: timestamppb.New(h.End),
})
}
return &controlapi.SessionHistoryResponse{History: resp}, nil
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The SessionHistory API returns session history entries from a map, which has a randomized iteration order in Go. This makes the returned list unstable and inconsistent across calls. Sorting the history chronologically by Start time provides a deterministic and stable response. Note that you will need to import the "sort" package in control/control.go.

func (c *Controller) SessionHistory(ctx context.Context, r *controlapi.SessionHistoryRequest) (*controlapi.SessionHistoryResponse, error) {
	history := c.opt.SessionManager.GetSessionHistory()
	var resp []*controlapi.SessionHistoryResponse_History
	for id, h := range history {
		resp = append(resp, &controlapi.SessionHistoryResponse_History{
			SessionID: id,
			Start:     timestamppb.New(h.Start),
			End:       timestamppb.New(h.End),
		})
	}
	sort.Slice(resp, func(i, j int) bool {
		return resp[i].Start.AsTime().Before(resp[j].Start.AsTime())
	})
	return &controlapi.SessionHistoryResponse{History: resp}, nil
}

@github-actions

github-actions Bot commented Jun 17, 2026

Copy link
Copy Markdown

⚠️ Are we earthbuild yet?

Warning: "earthly" occurrences have increased by 28 (12.9%)

📈 Overall Progress

Branch Total Count
main 217
This PR 245
Difference +28 (12.9%)

📁 Changes by file type:

File Type Change
Go files (.go) ❌ +25
Documentation (.md) ➖ No change
Earthfiles ➖ No change

Keep up the great work migrating from Earthly to Earthbuild! 🚀

💡 Tips for finding more occurrences

Run locally to see detailed breakdown:

./.github/scripts/count-earthly.sh

Note that the goal is not to reach 0.
There is anticipated to be at least some occurences of earthly in the source code due to backwards compatibility with config files and language constructs.

kmannislands and others added 7 commits June 17, 2026 11:40
The buildkitd/buildctl ldflags set version.Package to the legacy
github.com/earthly/buildkit. EarthBuild's earthly client compares the
buildkit daemon's reported package against github.com/EarthBuild/buildkit
and warns "Using a non-EarthBuild version of Buildkit is not supported"
on every invocation. Report the current org so our own buildkit is
recognised as supported.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The manual runtime.LockOSThread() in run() was added to stop runc
processes intermittently exiting with status -1 (the classic
PdeathSignal-vs-Go-scheduler race, golang/go#27505).

go-runc v1.1.0 (which the upstream merge pulled in) already handles
this: because we set Runc.PdeathSignal (updateRuncFieldsForHostOS),
Run() -> startCommand() takes the Monitor.StartLocked() path, which
calls runtime.LockOSThread() on the goroutine that actually does
cmd.Start() (the fork). Our manual lock was on the *caller* goroutine
-- not the forking one -- so it no longer does anything useful here.

Removing it restores executor_linux.go to the exact upstream version,
shrinking the fork delta with no behaviour change.
docopy() dropped action.ModeStr (string chmod, e.g. COPY --chmod=u+x)
and hard-disabled action.AlwaysReplaceExistingDestPaths, with a comment
claiming "not supported in this fsutil fork".

That comment is stale: the vendored fork (EarthBuild/fsutil, pulled in by
5638371) supports both -- copy.CopyInfo.ModeStr (copy.go:197) and
AlwaysReplaceExistingDestPaths (copy.go:208) are honoured. The removals
dated from an older fsutil and silently degraded string-mode COPY to the
integer-mode fallback. Restores the upstream code path; matches base.
Three changes that diverged from upstream with zero functional effect,
restored to reduce future merge conflicts:

- diffapply_linux.go: uint64(stat.Dev) -> stat.Dev (Dev is uint64 on all
  linux arches, the cast was a no-op) and Rdev: 0 -> unix.Mkdev(0, 0)
  (Mkdev(0,0) == 0).
- scheduler_test.go: context.TODO() -> t.Context() in two loop bodies.
  t.Context() (Go 1.24+) is available under go 1.25.5 and is what
  upstream uses; the downgrade was an older-toolchain artifact.
Reverts util/archutil to upstream: removes the /proc mount in the arch
check and the check_nolinux.go stub, and restores check_unix.go's
!windows build constraint (it had been narrowed to linux).

The patch worked around Rosetta failing to open /proc/self/exe inside
the check's chroot, but it is not needed here and carried two upstream
regressions: the !windows->linux narrowing dropped the arch check on
FreeBSD/Darwin, and the mount error was fatal, breaking the check on
rootless/unprivileged daemons. Restoring upstream shrinks the fork delta
and removes both regressions.
Removes the earthly diagnostic scaffolding added to chase the
"inconsistent graph state" / "return leaving incoming/outgoing open"
scheduler errors: the dgstTracker ring buffer
(inconsistent_graph_state_error_tracker.go), the helpMe context strings
and bklog error logging in scheduler.go dispatch/NewInputRequest, and the
dgstTracker.add call sites in jobs.go.

The bug is no longer being actively investigated, so this telemetry is no
longer needed. All three files now match upstream exactly, removing ~120
lines of delta from the scheduler/jobs hot path -- the most
merge-conflict-prone files in the fork.
maps.Clone(opt.FrontendAttrs) returns nil when opt.FrontendAttrs is nil,
and the following maps.Copy(frontendAttrs, cacheOpt.frontendAttrs) then
panics with "assignment to entry in nil map" whenever cache attrs are
present. This is hit on the Client.Build path (earthly's multiImageSolver
passes nil FrontendAttrs) with remote/inline cache configured.

Regression from upstream "simplify with maps pkg functions" (dfc3527),
which replaced a nil-safe explicit map init with maps.Clone/Copy. Present
in moby/buildkit master too -- candidate for upstreaming. Re-add the nil
guard so frontendAttrs is always writable.
The fork existed only to add a per-file VerboseProgressCB (verbose-only
transfer logging, 2022). Stock fsutil already provides the equivalent
per-file hook on receive via ReceiveOpt.NotifyHashed (ChangeFunc), which
carries path + FileInfo. Rewire the earthly filesync target callbacks to
NotifyHashed and use stock fsutil's 4-arg Send everywhere, removing every
VerboseProgressCB reference. Drop the go.mod replace directive and
re-vendor stock fsutil.

Client API: ExportEntry.VerboseProgressCB -> OnReceiveFile (fsutil.ChangeFunc).
Resolves the Dockerfile version-baking conflict: keep the branch's
worktree-aware heredoc block (skips version check for git worktrees,
sets dev stubs) and apply main's rebrand from #18
(EARTHLY_PKG github.com/earthly/buildkit -> github.com/EarthBuild/buildkit).
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.