fix: empty root-owned bind workspaces for non-root runners (#1399)

A job running as root leaves root-owned files in the bind-mounted workspace. A non-root runner on Linux cannot remove them, so workspaces pile up with only a warning. Such a runner now empties the workspace as root from inside the job container, right before removing the container. Root runners and Docker Desktop already remove these files themselves and skip this.

The workspace is only emptied when it is exactly the runner's own mountpoint with no mount below it. Otherwise, or when the command fails, the existing removal and warning apply. Jobs of a reusable workflow share the workspace, so they are not covered and still end in the existing warning.

Fixes: https://gitea.com/gitea/runner/issues/879
Co-authored-by: silverwind <[email protected]>
Co-authored-by: silverwind <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1399
Reviewed-by: silverwind <[email protected]>
Co-authored-by: bircni <[email protected]>
This commit is contained in:
authored and silverwind committed 2026-11-07 04:33:19 +00:00
1 parent 993389d428
commit 79faf5e55d
7 files changed
+193 -25

No files matched your search

+8
View File
@@ -15,6 +15,7 @@ import (
"strings"
"sync"
"sync/atomic"
"time"
"gitea.com/gitea/runner/act/common"
@@ -24,6 +25,13 @@ import (
var hostOS = runtime.GOOS // replaced in tests
// DrainGracePeriod bounds how long we wait for an output-copy goroutine to
// finish draining a container's output before returning, so that neither a
// cancellation (waitForCommand) nor a normal container exit (wait) truncates
// the tail of the log. It is a safety bound: in the common case the stream
// reaches EOF and the goroutine returns well before this elapses.
const DrainGracePeriod = 2 * time.Second
// ExitCodeError reports a non-zero process exit code from a container command.
type ExitCodeError int
+2 -9
View File
@@ -41,13 +41,6 @@ import (
specs "github.com/opencontainers/image-spec/specs-go/v1"
)
// drainGracePeriod bounds how long we wait for an output-copy goroutine to
// finish draining a container's output before returning, so that neither a
// cancellation (waitForCommand) nor a normal container exit (wait) truncates
// the tail of the log. It is a safety bound: in the common case the stream
// reaches EOF and the goroutine returns well before this elapses.
const drainGracePeriod = 2 * time.Second
// NewContainer creates a reference to a container
func NewContainer(input *NewContainerInput) ExecutionsEnvironment {
// Resolved up front because the image pull runs before the container is created.
@@ -947,7 +940,7 @@ func (cr *containerReference) waitForCommand(ctx context.Context, resp client.Hi
if err != nil {
logger.Debugf("Failed to drain command output after cancellation: %v", err)
}
case <-time.After(drainGracePeriod):
case <-time.After(DrainGracePeriod):
logger.Warn("Timed out draining command output after cancellation")
}
@@ -1122,7 +1115,7 @@ func (cr *containerReference) wait() common.Executor {
if cr.attachDone != nil {
select {
case <-cr.attachDone:
case <-time.After(drainGracePeriod):
case <-time.After(DrainGracePeriod):
logger.Warn("Timed out draining container output")
}
cr.attachDone = nil