nektos/act · error

failed to handle credentials: %s

Error message

failed to handle credentials: %s

What it means

startJobContainer resolves registry credentials for the job's container image via rc.handleCredentials, which interpolates the username/password expressions from workflow 'container.credentials' and (for GitHub-hosted images) can use GITHUB_TOKEN / CR_PAT style secrets. Failure is wrapped with '%s' (non-wrapping format) so the original cause text is appended verbatim.

Source

Thrown at pkg/runner/run_context.go:271

}

func (rc *RunContext) startJobContainer() common.Executor {
	return func(ctx context.Context) error {
		logger := common.Logger(ctx)
		image := rc.platformImage(ctx)
		rawLogger := logger.WithField("raw_output", true)
		logWriter := common.NewLineWriter(rc.commandHandler(ctx), func(s string) bool {
			if rc.Config.LogOutput {
				rawLogger.Infof("%s", s)
			} else {
				rawLogger.Debugf("%s", s)
			}
			return true
		})

		username, password, err := rc.handleCredentials(ctx)
		if err != nil {
			return fmt.Errorf("failed to handle credentials: %s", err)
		}

		logger.Infof("\U0001f680  Start image=%s", image)
		name := rc.jobContainerName()

		envList := make([]string, 0)

		envList = append(envList, fmt.Sprintf("%s=%s", "RUNNER_TOOL_CACHE", "/opt/hostedtoolcache"))
		envList = append(envList, fmt.Sprintf("%s=%s", "RUNNER_OS", "Linux"))
		envList = append(envList, fmt.Sprintf("%s=%s", "RUNNER_ARCH", container.RunnerArch(ctx)))
		envList = append(envList, fmt.Sprintf("%s=%s", "RUNNER_TEMP", "/tmp"))
		envList = append(envList, fmt.Sprintf("%s=%s", "LANG", "C.UTF-8")) // Use same locale as GitHub Actions

		ext := container.LinuxContainerEnvironmentExtensions{}
		binds, mounts := rc.GetBindsAndMounts()

		// specify the network to which the container will connect when `docker create` stage. (like execute command line: docker create --network <networkName> <image>)
		// if using service containers, will create a new network for the containers.

View on GitHub (pinned to 4f41128141)

Solutions

  1. Pass the required secrets: act -s CR_USERNAME=user -s CR_PASSWORD=pass and reference them in container.credentials.
  2. Verify the credential expressions in the workflow's 'container.credentials' interpolate to non-empty values.
  3. Docker-login once on the host (docker login ghcr.io) so the image can be pulled without workflow-level credentials.
  4. Check the appended cause text in the message — it names exactly which credential failed.

Example fix

# before (workflow)
container:
  image: ghcr.io/org/img:1
  credentials:
    username: ${{ secrets.CR_USER }}
    password: ${{ secrets.CR_PASS }}
# run without secrets

# after
act -s CR_USER=user -s CR_PASS=token -j build
Defensive patterns

Strategy: validation

Validate before calling

# fail fast if required credential secrets are absent for jobs with container images
for s in CR_USERNAME CR_PASSWORD; do
  [ -n "${!s}" ] || { echo "missing secret $s"; exit 1; }
done
docker login ghcr.io -u "$CR_USERNAME" -p "$CR_PASSWORD" >/dev/null && echo registry-auth-ok

Prevention

When it happens

Trigger: Job 'container: image: ...' block with 'credentials: username/password' whose expressions fail to evaluate, are empty, or reference missing secrets; handleCredentials returns an error when the image requires auth but no usable credentials resolve.

Common situations: Private registry (ghcr.io, ECR, Artifactory) image without passing secrets: act -s USERNAME=... -s PASSWORD=...; expression typo in credentials block; expired token.

Related errors


AI-assisted analysis of nektos/act@4f41128141 (2026-08-15). Data as JSON: /api/errors/21aee239bac6e1d5. Report an issue: GitHub.