fix: fix panics, enable the forcetypeassert lint (#1123)

An unchecked type assertion panics on input it did not expect, as a missing `tool_cache` key did in https://gitea.com/gitea/runner/pulls/1122.

Every flagged site is now handled where it can fail, or typed so it cannot: a `lock.Keyed` replaces the two `sync.Map` mutex registries, and the reporter's outputs carry an explicit sent flag. Mocks keep their assertions, a mismatch there is a setup error the panic names.

Bugs it turned up (only the first is reachable from workflows):

1. A scalar `matrix.include` or `matrix.exclude`, e.g. `include: foo` or `include: [1, 2]`, panicked the runner with `interface conversion: interface {} is string, not map[string]interface {}`. Verified against `main`, it is now a workflow error. `OnSchedule` panicked the same way on a malformed `on.schedule` entry.
1. `ExternalURL()` panicked on the nil listener after `Close()`, the port is now resolved once at startup.
1. `errors.Is(err, git.ErrShortRef)` followed by `err.(*git.Error)` panics as soon as anything wraps that error, so it is `errors.As` now.
1. An output name the server acknowledged without ever being sent one was recorded as sent forever, which silently dropped a later value for that name.

48576ab3e5 fixes one discovered issue: a matrix key holding a nested object was logged and then run as if the job had no matrix, so it now fails like an unknown `exclude` key.
Reviewed-on: https://gitea.com/gitea/runner/pulls/1123
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
silverwind
2026-07-30 07:09:53 +00:00
committed by silverwind
parent 0192861155
commit 3f7fd16ea1
15 changed files with 210 additions and 109 deletions

View File

@@ -129,6 +129,16 @@ func readActionImpl(ctx context.Context, step *model.Step, actionDir, actionPath
return action, err
}
// cachedActionTar returns the action's tree from the action cache, which only a remote action
// has an entry in.
func cachedActionTar(ctx context.Context, step actionStep, name, includePrefix string) (io.ReadCloser, error) {
remote, ok := step.(*stepActionRemote)
if !ok {
return nil, fmt.Errorf("action %q is a remote action but runs as %T", name, step)
}
return step.getRunContext().Config.ActionCache.GetTarArchive(ctx, remote.cacheDir, remote.resolvedSha, includePrefix)
}
func maybeCopyToActionDir(ctx context.Context, step actionStep, actionDir, actionPath, containerActionDir string) error {
logger := common.Logger(ctx)
rc := step.getRunContext()
@@ -147,8 +157,7 @@ func maybeCopyToActionDir(ctx context.Context, step actionStep, actionDir, actio
}
if rc.Config != nil && rc.Config.ActionCache != nil {
raction := step.(*stepActionRemote)
ta, err := rc.Config.ActionCache.GetTarArchive(ctx, raction.cacheDir, raction.resolvedSha, "")
ta, err := cachedActionTar(ctx, step, stepModel.Uses, "")
if err != nil {
return err
}
@@ -351,8 +360,7 @@ func execAsDocker(ctx context.Context, step actionStep, actionName, actionDir, b
}
defer buildContext.Close()
} else if rc.Config.ActionCache != nil {
rstep := step.(*stepActionRemote)
buildContext, err = rc.Config.ActionCache.GetTarArchive(ctx, rstep.cacheDir, rstep.resolvedSha, contextDir)
buildContext, err = cachedActionTar(ctx, step, actionName, contextDir)
if err != nil {
return err
}

View File

@@ -22,13 +22,13 @@ import (
"runtime"
"slices"
"strings"
"sync"
"time"
"gitea.com/gitea/runner/act/common"
"gitea.com/gitea/runner/act/container"
"gitea.com/gitea/runner/act/exprparser"
"gitea.com/gitea/runner/act/model"
"gitea.com/gitea/runner/internal/pkg/lock"
"github.com/docker/cli/cli/compose/loader"
"github.com/docker/go-connections/nat"
@@ -715,13 +715,10 @@ func (rc *RunContext) ActionCacheDir() string {
// jobMutexes serializes per-job result/output aggregation across the matrix combinations that
// share one *model.Job and run in parallel. Keyed by the shared *model.Job (mirrors the
// per-directory AcquireCloneLock pattern).
var jobMutexes sync.Map // key: *model.Job; value: *sync.Mutex
var jobMutexes lock.Keyed[*model.Job]
func lockJob(job *model.Job) func() {
v, _ := jobMutexes.LoadOrStore(job, &sync.Mutex{})
mu := v.(*sync.Mutex)
mu.Lock()
return mu.Unlock
return jobMutexes.Lock(job)
}
func (rc *RunContext) interpolateOutputs() common.Executor {

View File

@@ -138,9 +138,10 @@ func (sar *stepActionRemote) prepareActionExecutor() common.Executor {
})
var ntErr common.Executor
if err := gitClone(ctx); err != nil {
if errors.Is(err, git.ErrShortRef) {
var refErr *git.Error
if errors.As(err, &refErr) && errors.Is(err, git.ErrShortRef) {
return fmt.Errorf("Unable to resolve action `%s`, the provided ref `%s` is the shortened version of a commit SHA, which is not supported. Please use the full commit SHA `%s` instead",
sar.Step.Uses, sar.remoteAction.Ref, err.(*git.Error).Commit())
sar.Step.Uses, sar.remoteAction.Ref, refErr.Commit())
} else if errors.Is(err, gogit.ErrForceNeeded) { // TODO: figure out if it will be easy to shadow/alias go-git err's
ntErr = common.NewInfoExecutor("Non-terminating error while running 'git clone': %v", err)
} else {