Repository: nektos/act (language: go)

Issue:
Composite action inputs clobbered
### Bug report info

```plain text
$ act --bug-report
act version:            0.2.30
GOOS:                   linux
GOARCH:                 amd64
NumCPU:                 8
Docker host:            DOCKER_HOST environment variable is unset/empty.
Sockets found:
        /var/run/docker.sock
Config files:
        /home/gwarner/.actrc:
                -P ubuntu-latest=catthehacker/ubuntu:act-latest
                -P ubuntu-20.04=catthehacker/ubuntu:act-20.04
                -P ubuntu-18.04=catthehacker/ubuntu:act-18.04
                ubuntu-16.04=catthehacker/ubuntu:act-16.04
Build info:
        Go version:            go1.18.5
        Module path:           github.com/nektos/act
        Main version:          (devel)
        Main path:             github.com/nektos/act
        Main checksum:
        Build settings:
                -compiler:            gc
                CGO_ENABLED:          1
                GOARCH:               amd64
                GOOS:                 linux
                GOAMD64:              v1
Docker Engine:
        Engine version:        20.10.17
        Engine runtime:        runc
        Cgroup version:        2
        Cgroup driver:         systemd
        Storage driver:        overlay2
        Registry URI:          https://index.docker.io/v1/
        OS:                    NixOS 22.05 (Quokka)
        OS type:               linux
        OS version:            22.05
        OS arch:               x86_64
        OS kernel:             5.15.60
        OS CPU:                8
        OS memory:             63521 MB
        Security options:
                name=seccomp,profile=default
                name=cgroupns
```


### Command used with act

```sh
act -P ubuntu-18.04=nektos/act-environments-ubuntu:18.04 -W .github/workflows/pipeline.yaml
```


### Describe issue

In action, `${{ inputs... }}` not available after 'using' actions/setup-node or actions/setup-java.

### Link to GitHub repository

https://github.com/gdw2/act-test

### Workflow content

```yml
name: Pipeline
on:
  push:

jobs:
  test-orders:
    runs-on: [ viarise ]
    steps:
      - uses: actions/checkout@v2
      - uses: ./.github/actions/test
        with:
          module: some-module


--- (action below) ---

name: Test Action
inputs:
  module:
    required: true
    description: some description

runs:
  using: composite
  steps:
    - shell: bash
      run: |
        echo TEST1 ${{ inputs.module }}
    - uses: actions/setup-node@v2
      with:
        node-version: '16'
    - shell: bash
      run: |
        echo TEST2 ${{ inputs.module }}.   # <-- ⛔️ This isn't echoing.  `inputs` seems to be clobbered by previous step.
```


### Relevant log output

```sh
Pipeline/test-orders] 🚀  Start image=nektos/act-environments-ubuntu:18.04
<snip>
| TEST1 some-module
[Pipeline/test-orders]     Success - Main echo TEST1 ${{ inputs.module }}

<snip>
[Pipeline/test-orders]  Runle Main actions/setup-node@v2
<snip>
[Pipeline/test-orders]     ##Success - Main actions/setup-/ode@v2
<snip>
| TEST2         <-- ⛔️ This should say "TEST2 some-module" instead!!!!
<snip>
[Pipeline/test-orders] 🏁  Job succeeded
```


### Additional information

I would expect it to echo "TEST2 some-module", but using actions/setup-node seems to clobber `inputs` so that `${{ inputs.module }}` is empty.

When I run it verbosely, after actions/setup-node, I no longer see `INPUT_MODULE` being set for subsequent steps.

**This works fine on GHA on GHE, which is why I think it might be an act issue.**


Discussion / hints:
I also want to leave open the possibility that I have no idea what I'm doing.
This sounds like an issue of this change https://github.com/nektos/act/commit/e3608115706382233c3b943b4dabea09bab9d7d2
Does act v0.2.26 work? The release before composite logic changes.
Hello Everyone,

I am facing the same issue and I just wanted to confirm that the issue is not present in version `0.2.26`. Please find some logs below that compare a similar workflow using the action but with two different versions (`0.2.26` & `0.2.30`).

## Yaml samples

### `workflow.yml`

```yaml
name: Test workflow
on:
  push:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Clone
        uses: actions/checkout@v3

      - name: Test action inputs
        uses: ./test/
        with:
          a: foo
          b: bar
          c: foobar
```

### `test/action.yml`

```yaml
name:  test

inputs:
  a:
    description: "test input a"
    required: false
  b:
    description: "test input b"
    required: false
  c:
    description: "test input c"
    required: false

runs:
  using: "composite"
  steps:
      - name: Echo inputs (start)
        run: echo "${{ toJSON(inputs) }}"
        shell: bash

      - name: Simple step
        run: echo "do something.."
        shell: bash
        if: inputs.a

      - name: Echo inputs (after run step)
        run: echo "${{ toJSON(inputs) }}"
        shell: bash

      - name: Simple action
        uses: actions/hello-world-docker-action@master
        with:
          who-to-greet: 'Mona the Octocat'

      - name: Echo inputs (after action call without with statement)
        run: echo "${{ toJSON(inputs) }}"
        shell: bash
```

## Results

### using `act@v0.2.26`

```
[Test workflow/test] ⭐  Run Clone
[Test workflow/test]   ✅  Success - Clone
[Test workflow/test] ⭐  Run Test action inputs
[Test workflow/test] ⭐  Run Echo inputs (start)
[Test workflow/test]   🐳  docker exec cmd=[bash --noprofile --norc -e -o pipefail /var/run/act/workflow/1-composite-0.sh] user= workdir=
| {
|   a: foo,
|   b: bar,
|   c: foobar
| }
[Test workflow/test]   ✅  Success - Echo inputs (start)
[Test workflow/test] ⭐  Run Simple step
[Test workflow/test]   🐳  docker exec cmd=[bash --noprofile --norc -e -o pipefail /var/run/act/workflow/1-composite-1.sh] user= workdir=
| do something..
[Test workflow/test]   ✅  Success - Simple step
[Test workflow/test] ⭐  Run Echo inputs (after run step)
[Test workflow/test]   🐳  docker exec cmd=[bash --noprofile --norc -e -o pipefail /var/run/act/workflow/1-composite-2.sh] user= workdir=
| {
|   a: foo,
|   b: bar,
|   c: foobar
| }
[Test workflow/test]   ✅  Success - Echo inputs (after run step)
[Test workflow/test] ⭐  Run Simple action
[Test workflow/test]   ☁  git clone 'https://github.com/actions/hello-world-docker-action' # ref=master
[Test workflow/test]   🐳  docker pull image=act-actions-hello-world-docker-action-master-dockeraction:latest platform= username= forcePull=false
[Test workflow/test]   🐳  docker create image=act-actions-hello-world-docker-action-master-dockeraction:latest platform= entrypoint=[] cmd=["Mona the Octocat"]
[Test workflow/test]   🐳  docker run image=act-actions-hello-world-docker-action-master-dockeraction:latest platform= entrypoint=[] cmd=["Mona the Octocat"]
| Hello Mona the Octocat
[Test workflow/test]   ⚙  ::set-output:: time=Fri Aug 26 17:46:49 UTC 2022
[Test workflow/test]   ✅  Success - Simple action
[Test workflow/test] ⭐  Run Echo inputs (after action call without with statement)
[Test workflow/test]   🐳  docker exec cmd=[bash --noprofile --norc -e -o pipefail /var/run/act/workflow/1-composite-4.sh] user= workdir=
| {
|   a: foo,
|   b: bar,                      <------- THIS IS CORRECT!
|   c: foobar
| }
[Test workflow/test]   ✅  Success - Echo inputs (after action call without with statement)
[Test workflow/test]   ✅  Success - Test action inputs
```

### using `act@v0.2.30`

```
[Test workflow/test] ⭐ Run Main Clone
[Test workflow/test]   ✅  Success - Main Clone
[Test workflow/test] ⭐ Run Main Test action inputs
[Test workflow/test] ⭐ Run Main Echo inputs (start)
[Test workflow/test]   🐳  docker exec cmd=[bash --noprofile --norc -e -o pipefail /var/run/act/workflow/1-composite-0] user= workdir=
| {
|   a: foo,
|   b: bar,
|   c: foobar
| }
[Test workflow/test]   ✅  Success - Main Echo inputs (start)
[Test workflow/test] ⭐ Run Main Simple step
[Test workflow/test]   🐳  docker exec cmd=[bash --noprofile --norc -e -o pipefail /var/run/act/workflow/1-composite-1] user= workdir=
| do something..
[Test workflow/test]   ✅  Success - Main Simple step
[Test workflow/test] ⭐ Run Main Echo inputs (after run step)
[Test workflow/test]   🐳  docker exec cmd=[bash --noprofile --norc -e -o pipefail /var/run/act/workflow/1-composite-2] user= workdir=
| {
|   a: foo,
|   b: bar,
|   c: foobar
| }
[Test workflow/test]   ✅  Success - Main Echo inputs (after run step)
[Test workflow/test]   ☁  git clone 'https://github.com/actions/hello-world-docker-action' # ref=master
[Test workflow/test] ⭐ Run Main Simple action
[Test workflow/test]   🐳  docker build -t act-actions-hello-world-docker-action-master-dockeraction:latest /Users/solal/.cache/act/actions-hello-world-docker-action@master
[Test workflow/test]   🐳  docker pull image=act-actions-hello-world-docker-action-master-dockeraction:latest platform= username= forcePull=false
[Test workflow/test]   🐳  docker create image=act-actions-hello-world-docker-action-master-dockeraction:latest platform= entrypoint=[] cmd=["Mona the Octocat"]
[Test workflow/test]   🐳  docker run image=act-actions-hello-world-docker-action-master-dockeraction:latest platform= entrypoint=[] cmd=["Mona the Octocat"]
| Hello Mona the Octocat
[Test workflow/test]   ⚙  ::set-output:: time=Fri Aug 26 16:34:23 UTC 2022
[Test workflow/test]   ✅  Success - Main Simple action
[Test workflow/test] ⭐ Run Main Echo inputs (after action call without with statement)
[Test workflow/test]   🐳  docker exec cmd=[bash --noprofile --norc -e -o pipefail /var/run/act/workflow/1-composite-4] user= workdir=
| {
|   who-to-greet: Mona the Octocat.  <------- THIS IS WRONG!
| }
[Test workflow/test]   ✅  Success - Main Echo inputs (after action call without with statement)
[Test workflow/test]   ✅  Success - Main Test action inputs
[Test workflow/test] ⭐ Run Post Test action inputs
[Test workflow/test]   ✅  Success - Post Test action inputs
[Test workflow/test] 🏁  Job succeeded
```

## Additional details

As you can observe at the end of the output when `act@0.2.30` is used, inputs are replaced by the one specified in the previous action.
I will take care of that. Might need a week before I can start looking into it.
it works with `act@0.2.31`

```diff
// try
- ${{ github.event.inputs.key }}
// instead of
+ ${{ inputs.key }}
```

Fix patch:
diff --git a/pkg/model/workflow.go b/pkg/model/workflow.go
index 86ec67f6644..af7123191d5 100644
--- a/pkg/model/workflow.go
+++ b/pkg/model/workflow.go
@@ -215,7 +215,8 @@ func (j *Job) Matrix() map[string][]interface{} {
 
 // GetMatrixes returns the matrix cross product
 // It skips includes and hard fails excludes for non-existing keys
-// nolint:gocyclo
+//
+//nolint:gocyclo
 func (j *Job) GetMatrixes() []map[string]interface{} {
 	matrixes := make([]map[string]interface{}, 0)
 	if j.Strategy != nil {
@@ -376,8 +377,16 @@ func (s *Step) String() string {
 }
 
 // Environments returns string-based key=value map for a step
+// Note: all keys are uppercase
 func (s *Step) Environment() map[string]string {
-	return environment(s.Env)
+	env := environment(s.Env)
+
+	for k, v := range env {
+		delete(env, k)
+		env[strings.ToUpper(k)] = v
+	}
+
+	return env
 }
 
 // GetEnv gets the env for a step
@@ -436,6 +445,22 @@ const (
 	StepTypeInvalid
 )
 
+func (s StepType) String() string {
+	switch s {
+	case StepTypeInvalid:
+		return "invalid"
+	case StepTypeRun:
+		return "run"
+	case StepTypeUsesActionLocal:
+		return "local-action"
+	case StepTypeUsesActionRemote:
+		return "remote-action"
+	case StepTypeUsesDockerURL:
+		return "docker"
+	}
+	return "unknown"
+}
+
 // Type returns the type of the step
 func (s *Step) Type() StepType {
 	if s.Run == "" && s.Uses == "" {
diff --git a/pkg/runner/action.go b/pkg/runner/action.go
index 97674c86fa3..2dee988c900 100644
--- a/pkg/runner/action.go
+++ b/pkg/runner/action.go
@@ -136,26 +136,10 @@ func runActionImpl(step actionStep, actionDir string, remoteAction *remoteAction
 		action := step.getActionModel()
 		logger.Debugf("About to run action %v", action)
 
-		if remoteAction != nil {
-			rc.ActionRepository = fmt.Sprintf("%s/%s", remoteAction.Org, remoteAction.Repo)
-			rc.ActionRef = remoteAction.Ref
-		} else {
-			rc.ActionRepository = ""
-			rc.ActionRef = ""
+		err := setupActionEnv(ctx, step, remoteAction)
+		if err != nil {
+			return err
 		}
-		defer (func() {
-			// cleanup after the action is done, to avoid side-effects in
-			// the next step/action
-			rc.ActionRepository = ""
-			rc.ActionRef = ""
-		})()
-
-		// we need to merge with github-env again, since at the step setup
-		// time, we don't have all environment prepared
-		mergeIntoMap(step.getEnv(), rc.withGithubEnv(ctx, map[string]string{}))
-
-		populateEnvsFromSavedState(step.getEnv(), step, rc)
-		populateEnvsFromInput(ctx, step.getEnv(), action, rc)
 
 		actionLocation := path.Join(actionDir, actionPath)
 		actionName, containerActionDir := getContainerActionPaths(stepModel, actionLocation, rc)
@@ -169,6 +153,7 @@ func runActionImpl(step actionStep, actionDir string, remoteAction *remoteAction
 			}
 			containerArgs := []string{"node", path.Join(containerActionDir, action.Runs.Main)}
 			logger.Debugf("executing remote job container: %s", containerArgs)
+
 			return rc.execJobContainer(containerArgs, *step.getEnv(), "", "")(ctx)
 		case model.ActionRunsUsingDocker:
 			location := actionLocation
@@ -193,6 +178,20 @@ func runActionImpl(step actionStep, actionDir string, remoteAction *remoteAction
 	}
 }
 
+func setupActionEnv(ctx context.Context, step actionStep, remoteAction *remoteAction) error {
+	rc := step.getRunContext()
+
+	// A few fields in the environment (e.g. GITHUB_ACTION_REPOSITORY)
+	// are dependent on the action. That means we can complete the
+	// setup only after resolving the whole action model and cloning
+	// the action
+	rc.withGithubEnv(ctx, step.getGithubContext(ctx), *step.getEnv())
+	populateEnvsFromSavedState(step.getEnv(), step, rc)
+	populateEnvsFromInput(ctx, step.getEnv(), step.getActionModel(), rc)
+
+	return nil
+}
+
 // https://github.com/nektos/act/issues/228#issuecomment-629709055
 // files in .gitignore are not copied in a Docker container
 // this causes issues with actions that ignore other important resources
@@ -211,7 +210,8 @@ func removeGitIgnore(ctx context.Context, directory string) error {
 }
 
 // TODO: break out parts of function to reduce complexicity
-// nolint:gocyclo
+//
+//nolint:gocyclo
 func execAsDocker(ctx context.Context, step actionStep, actionName string, basedir string, localAction bool) error {
 	logger := common.Logger(ctx)
 	rc := step.getRunContext()
@@ -299,11 +299,8 @@ func execAsDocker(ctx context.Context, step actionStep, actionName string, based
 func evalDockerArgs(ctx context.Context, step step, action *model.Action, cmd *[]string) {
 	rc := step.getRunContext()
 	stepModel := step.getStepModel()
-	oldInputs := rc.Inputs
-	defer func() {
-		rc.Inputs = oldInputs
-	}()
-	inputs := make(map[string]interface{})
+
+	inputs := make(map[string]string)
 	eval := rc.NewExpressionEvaluator(ctx)
 	// Set Defaults
 	for k, input := range action.Inputs {
@@ -314,7 +311,8 @@ func evalDockerArgs(ctx context.Context, step step, action *model.Action, cmd *[
 			inputs[k] = eval.Interpolate(ctx, v)
 		}
 	}
-	rc.Inputs = inputs
+	mergeIntoMap(step.getEnv(), inputs)
+
 	stepEE := rc.NewStepExpressionEvaluator(ctx, step)
 	for i, v := range *cmd {
 		(*cmd)[i] = stepEE.Interpolate(ctx, v)
@@ -372,29 +370,6 @@ func newStepContainer(ctx context.Context, step step, image string, cmd []string
 	return stepContainer
 }
 
-func (rc *RunContext) setupActionInputs(ctx context.Context, step actionStep) {
-	if step.getActionModel() == nil {
-		// e.g. local checkout skip has no action model
-		return
-	}
-
-	stepModel := step.getStepModel()
-	action := step.getActionModel()
-
-	eval := rc.NewExpressionEvaluator(ctx)
-	inputs := make(map[string]interface{})
-	for k, input := range action.Inputs {
-		inputs[k] = eval.Interpolate(ctx, input.Default)
-	}
-	if stepModel.With != nil {
-		for k, v := range stepModel.With {
-			inputs[k] = eval.Interpolate(ctx, v)
-		}
-	}
-
-	rc.Inputs = inputs
-}
-
 func populateEnvsFromSavedState(env *map[string]string, step actionStep, rc *RunContext) {
 	stepResult := rc.StepResults[step.getStepModel().ID]
 	if stepResult != nil {
@@ -513,7 +488,10 @@ func runPreStep(step actionStep) common.Executor {
 			return rc.execJobContainer(containerArgs, *step.getEnv(), "", "")(ctx)
 
 		case model.ActionRunsUsingComposite:
-			step.getCompositeRunContext(ctx).updateCompositeRunContext(ctx, step.getRunContext(), step)
+			if step.getCompositeSteps() == nil {
+				step.getCompositeRunContext(ctx)
+			}
+
 			return step.getCompositeSteps().pre(ctx)
 
 		default:
@@ -601,7 +579,6 @@ func runPostStep(step actionStep) common.Executor {
 				return err
 			}
 
-			step.getCompositeRunContext(ctx).updateCompositeRunContext(ctx, step.getRunContext(), step)
 			return step.getCompositeSteps().post(ctx)
 
 		default:
diff --git a/pkg/runner/action_composite.go b/pkg/runner/action_composite.go
index 5dbfde68ec1..256d36ecfdd 100644
--- a/pkg/runner/action_composite.go
+++ b/pkg/runner/action_composite.go
@@ -3,37 +3,43 @@ package runner
 import (
 	"context"
 	"fmt"
+	"regexp"
+	"strings"
 
 	"github.com/nektos/act/pkg/common"
 	"github.com/nektos/act/pkg/model"
 )
 
-func evaluteCompositeInputAndEnv(ctx context.Context, parent *RunContext, step actionStep) (inputs map[string]interface{}, env map[string]string) {
-	eval := parent.NewExpressionEvaluator(ctx)
-
-	inputs = make(map[string]interface{})
-	for k, input := range step.getActionModel().Inputs {
-		inputs[k] = eval.Interpolate(ctx, input.Default)
-	}
-	if step.getStepModel().With != nil {
-		for k, v := range step.getStepModel().With {
-			inputs[k] = eval.Interpolate(ctx, v)
+func evaluateCompositeInputAndEnv(ctx context.Context, parent *RunContext, step actionStep) map[string]string {
+	env := make(map[string]string)
+	stepEnv := *step.getEnv()
+	for k, v := range stepEnv {
+		// do not set current inputs into composite action
+		// the required inputs are added in the second loop
+		if !strings.HasPrefix(k, "INPUT_") {
+			env[k] = v
 		}
 	}
 
-	env = make(map[string]string)
-	for k, v := range parent.Env {
-		env[k] = eval.Interpolate(ctx, v)
-	}
-	for k, v := range step.getStepModel().Environment() {
-		env[k] = eval.Interpolate(ctx, v)
+	for inputID, input := range step.getActionModel().Inputs {
+		envKey := regexp.MustCompile("[^A-Z0-9-]").ReplaceAllString(strings.ToUpper(inputID), "_")
+		envKey = fmt.Sprintf("INPUT_%s", strings.ToUpper(envKey))
+
+		// lookup if key is defined in the step but the the already
+		// evaluated value from the environment
+		_, defined := step.getStepModel().With[inputID]
+		if value, ok := stepEnv[envKey]; defined && ok {
+			env[envKey] = value
+		} else {
+			env[envKey] = input.Default
+		}
 	}
 
-	return inputs, env
+	return env
 }
 
 func newCompositeRunContext(ctx context.Context, parent *RunContext, step actionStep, actionPath string) *RunContext {
-	inputs, env := evaluteCompositeInputAndEnv(ctx, parent, step)
+	env := evaluateCompositeInputAndEnv(ctx, parent, step)
 
 	// run with the global config but without secrets
 	configCopy := *(parent.Config)
@@ -52,57 +58,42 @@ func newCompositeRunContext(ctx context.Context, parent *RunContext, step action
 				},
 			},
 		},
-		Config:           &configCopy,
-		StepResults:      map[string]*model.StepResult{},
-		JobContainer:     parent.JobContainer,
-		Inputs:           inputs,
-		ActionPath:       actionPath,
-		ActionRepository: parent.ActionRepository,
-		ActionRef:        parent.ActionRef,
-		Env:              env,
-		Masks:            parent.Masks,
-		ExtraPath:        parent.ExtraPath,
-		Parent:           parent,
+		Config:       &configCopy,
+		StepResults:  map[string]*model.StepResult{},
+		JobContainer: parent.JobContainer,
+		ActionPath:   actionPath,
+		Env:          env,
+		Masks:        parent.Masks,
+		ExtraPath:    parent.ExtraPath,
+		Parent:       parent,
 	}
 
 	return compositerc
 }
 
-// This updates a composite context inputs, env and masks.
-// This is needed to re-evalute/update that context between pre/main/post steps.
-// Some of the inputs/env may requires the results of in-between steps.
-func (rc *RunContext) updateCompositeRunContext(ctx context.Context, parent *RunContext, step actionStep) {
-	inputs, env := evaluteCompositeInputAndEnv(ctx, parent, step)
-
-	rc.Inputs = inputs
-	rc.Env = env
-	rc.Masks = append(rc.Masks, parent.Masks...)
-}
-
 func execAsComposite(step actionStep) common.Executor {
 	rc := step.getRunContext()
 	action := step.getActionModel()
 
 	return func(ctx context.Context) error {
-		compositerc := step.getCompositeRunContext(ctx)
+		compositeRC := step.getCompositeRunContext(ctx)
 
 		steps := step.getCompositeSteps()
 
-		ctx = WithCompositeLogger(ctx, &compositerc.Masks)
+		ctx = WithCompositeLogger(ctx, &compositeRC.Masks)
 
-		compositerc.updateCompositeRunContext(ctx, rc, step)
 		err := steps.main(ctx)
 
 		// Map outputs from composite RunContext to job RunContext
-		eval := compositerc.NewExpressionEvaluator(ctx)
+		eval := compositeRC.NewExpressionEvaluator(ctx)
 		for outputName, output := range action.Outputs {
 			rc.setOutput(ctx, map[string]string{
 				"name": outputName,
 			}, eval.Interpolate(ctx, output.Value))
 		}
 
-		rc.Masks = append(rc.Masks, compositerc.Masks...)
-		rc.ExtraPath = compositerc.ExtraPath
+		rc.Masks = append(rc.Masks, compositeRC.Masks...)
+		rc.ExtraPath = compositeRC.ExtraPath
 
 		return err
 	}
diff --git a/pkg/runner/expression.go b/pkg/runner/expression.go
index d803446d716..a0f0c9b8e4d 100644
--- a/pkg/runner/expression.go
+++ b/pkg/runner/expression.go
@@ -39,6 +39,13 @@ func (rc *RunContext) NewExpressionEvaluator(ctx context.Context) ExpressionEval
 		}
 	}
 
+	inputs := make(map[string]interface{})
+	for k, v := range rc.GetEnv() {
+		if strings.HasPrefix(k, "INPUT_") {
+			inputs[strings.ToLower(strings.TrimPrefix(k, "INPUT_"))] = v
+		}
+	}
+
 	ee := &exprparser.EvaluationEnvironment{
 		Github: rc.getGithubContext(ctx),
 		Env:    rc.GetEnv(),
@@ -56,7 +63,7 @@ func (rc *RunContext) NewExpressionEvaluator(ctx context.Context) ExpressionEval
 		Strategy: strategy,
 		Matrix:   rc.Matrix,
 		Needs:    using,
-		Inputs:   rc.Inputs,
+		Inputs:   inputs,
 	}
 	return expressionEvaluator{
 		interpreter: exprparser.NewInterpeter(ee, exprparser.Config{
@@ -87,8 +94,15 @@ func (rc *RunContext) NewStepExpressionEvaluator(ctx context.Context, step step)
 		}
 	}
 
+	inputs := make(map[string]interface{})
+	for k, v := range *step.getEnv() {
+		if strings.HasPrefix(k, "INPUT_") {
+			inputs[strings.ToLower(strings.TrimPrefix(k, "INPUT_"))] = v
+		}
+	}
+
 	ee := &exprparser.EvaluationEnvironment{
-		Github: rc.getGithubContext(ctx),
+		Github: step.getGithubContext(ctx),
 		Env:    *step.getEnv(),
 		Job:    rc.getJobContext(),
 		Steps:  rc.getStepsContext(),
@@ -104,7 +118,7 @@ func (rc *RunContext) NewStepExpressionEvaluator(ctx context.Context, step step)
 		Needs:    using,
 		// todo: should be unavailable
 		// but required to interpolate/evaluate the inputs in actions/composite
-		Inputs: rc.Inputs,
+		Inputs: inputs,
 	}
 	return expressionEvaluator{
 		interpreter: exprparser.NewInterpeter(ee, exprparser.Config{
diff --git a/pkg/runner/run_context.go b/pkg/runner/run_context.go
index 8c2de755ef6..facdef0ee9d 100644
--- a/pkg/runner/run_context.go
+++ b/pkg/runner/run_context.go
@@ -27,25 +27,22 @@ const ActPath string = "/var/run/act"
 
 // RunContext contains info about current job
 type RunContext struct {
-	Name             string
-	Config           *Config
-	Matrix           map[string]interface{}
-	Run              *model.Run
-	EventJSON        string
-	Env              map[string]string
-	ExtraPath        []string
-	CurrentStep      string
-	StepResults      map[string]*model.StepResult
-	ExprEval         ExpressionEvaluator
-	JobContainer     container.Container
-	OutputMappings   map[MappableOutput]MappableOutput
-	JobName          string
-	ActionPath       string
-	ActionRef        string
-	ActionRepository string
-	Inputs           map[string]interface{}
-	Parent           *RunContext
-	Masks            []string
+	Name           string
+	Config         *Config
+	Matrix         map[string]interface{}
+	Run            *model.Run
+	EventJSON      string
+	Env            map[string]string
+	ExtraPath      []string
+	CurrentStep    string
+	StepResults    map[string]*model.StepResult
+	ExprEval       ExpressionEvaluator
+	JobContainer   container.Container
+	OutputMappings map[MappableOutput]MappableOutput
+	JobName        string
+	ActionPath     string
+	Parent         *RunContext
+	Masks          []string
 }
 
 func (rc *RunContext) AddMask(mask string) {
@@ -438,8 +435,6 @@ func (rc *RunContext) getGithubContext(ctx context.Context) *model.GithubContext
 		Action:           rc.CurrentStep,
 		Token:            rc.Config.Token,
 		ActionPath:       rc.ActionPath,
-		ActionRef:        rc.ActionRef,
-		ActionRepository: rc.ActionRepository,
 		RepositoryOwner:  rc.Config.Env["GITHUB_REPOSITORY_OWNER"],
 		RetentionDays:    rc.Config.Env["GITHUB_RETENTION_DAYS"],
 		RunnerPerflog:    rc.Config.Env["RUNNER_PERFLOG"],
@@ -557,8 +552,7 @@ func nestedMapLookup(m map[string]interface{}, ks ...string) (rval interface{})
 	}
 }
 
-func (rc *RunContext) withGithubEnv(ctx context.Context, env map[string]string) map[string]string {
-	github := rc.getGithubContext(ctx)
+func (rc *RunContext) withGithubEnv(ctx context.Context, github *model.GithubContext, env map[string]string) map[string]string {
 	env["CI"] = "true"
 	env["GITHUB_ENV"] = ActPath + "/workflow/envs.txt"
 	env["GITHUB_PATH"] = ActPath + "/workflow/paths.txt"
diff --git a/pkg/runner/step.go b/pkg/runner/step.go
index 5b37ce34260..8682d5821df 100644
--- a/pkg/runner/step.go
+++ b/pkg/runner/step.go
@@ -16,6 +16,7 @@ type step interface {
 	post() common.Executor
 
 	getRunContext() *RunContext
+	getGithubContext(ctx context.Context) *model.GithubContext
 	getStepModel() *model.Step
 	getEnv() *map[string]string
 	getIfExpression(context context.Context, stage stepStage) string
@@ -136,9 +137,10 @@ func setupEnv(ctx context.Context, step step) error {
 	if err != nil {
 		return err
 	}
-	mergeIntoMap(step.getEnv(), step.getStepModel().GetEnv()) // step env should not be overwritten
+	// merge step env last, since it should not be overwritten
+	mergeIntoMap(step.getEnv(), step.getStepModel().GetEnv())
 
-	exprEval := rc.NewStepExpressionEvaluator(ctx, step)
+	exprEval := rc.NewExpressionEvaluator(ctx)
 	for k, v := range *step.getEnv() {
 		(*step.getEnv())[k] = exprEval.Interpolate(ctx, v)
 	}
@@ -169,7 +171,7 @@ func mergeEnv(ctx context.Context, step step) {
 		(*env)["PATH"] += `:` + p
 	}
 
-	mergeIntoMap(env, rc.withGithubEnv(ctx, *env))
+	rc.withGithubEnv(ctx, step.getGithubContext(ctx), *env)
 }
 
 func isStepEnabled(ctx context.Context, expr string, step step, stage stepStage) (bool, error) {
diff --git a/pkg/runner/step_action_local.go b/pkg/runner/step_action_local.go
index df73979f720..6b4fc062648 100644
--- a/pkg/runner/step_action_local.go
+++ b/pkg/runner/step_action_local.go
@@ -4,7 +4,6 @@ import (
 	"archive/tar"
 	"context"
 	"io"
-	"io/ioutil"
 	"os"
 	"path"
 	"path/filepath"
@@ -55,7 +54,7 @@ func (sal *stepActionLocal) main() common.Executor {
 			}
 		}
 
-		actionModel, err := sal.readAction(ctx, sal.Step, actionDir, "", localReader(ctx), ioutil.WriteFile)
+		actionModel, err := sal.readAction(ctx, sal.Step, actionDir, "", localReader(ctx), os.WriteFile)
 		if err != nil {
 			return err
 		}
@@ -74,6 +73,10 @@ func (sal *stepActionLocal) getRunContext() *RunContext {
 	return sal.RunContext
 }
 
+func (sal *stepActionLocal) getGithubContext(ctx context.Context) *model.GithubContext {
+	return sal.getRunContext().getGithubContext(ctx)
+}
+
 func (sal *stepActionLocal) getStepModel() *model.Step {
 	return sal.Step
 }
diff --git a/pkg/runner/step_action_remote.go b/pkg/runner/step_action_remote.go
index b0f23c12fcd..6616335d417 100644
--- a/pkg/runner/step_action_remote.go
+++ b/pkg/runner/step_action_remote.go
@@ -5,7 +5,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"os"
 	"path"
 	"path/filepath"
@@ -49,7 +48,7 @@ func (sar *stepActionRemote) prepareActionExecutor() common.Executor {
 
 		sar.remoteAction.URL = sar.RunContext.Config.GitHubInstance
 
-		github := sar.RunContext.getGithubContext(ctx)
+		github := sar.getGithubContext(ctx)
 		if sar.remoteAction.IsCheckout() && isLocalCheckout(github, sar.Step) && !sar.RunContext.Config.NoSkipCheckout {
 			common.Logger(ctx).Debugf("Skipping local actions/checkout because workdir was already copied")
 			return nil
@@ -92,14 +91,10 @@ func (sar *stepActionRemote) prepareActionExecutor() common.Executor {
 		return common.NewPipelineExecutor(
 			ntErr,
 			func(ctx context.Context) error {
-				actionModel, err := sar.readAction(ctx, sar.Step, actionDir, sar.remoteAction.Path, remoteReader(ctx), ioutil.WriteFile)
+				actionModel, err := sar.readAction(ctx, sar.Step, actionDir, sar.remoteAction.Path, remoteReader(ctx), os.WriteFile)
 				sar.action = actionModel
 				return err
 			},
-			func(ctx context.Context) error {
-				sar.RunContext.setupActionInputs(ctx, sar)
-				return nil
-			},
 		)(ctx)
 	}
 }
@@ -116,7 +111,7 @@ func (sar *stepActionRemote) main() common.Executor {
 	return common.NewPipelineExecutor(
 		sar.prepareActionExecutor(),
 		runStepExecutor(sar, stepStageMain, func(ctx context.Context) error {
-			github := sar.RunContext.getGithubContext(ctx)
+			github := sar.getGithubContext(ctx)
 			if sar.remoteAction.IsCheckout() && isLocalCheckout(github, sar.Step) && !sar.RunContext.Config.NoSkipCheckout {
 				if sar.RunContext.Config.BindWorkdir {
 					common.Logger(ctx).Debugf("Skipping local actions/checkout because you bound your workspace")
@@ -129,9 +124,7 @@ func (sar *stepActionRemote) main() common.Executor {
 
 			actionDir := fmt.Sprintf("%s/%s", sar.RunContext.ActionCacheDir(), strings.ReplaceAll(sar.Step.Uses, "/", "-"))
 
-			return common.NewPipelineExecutor(
-				sar.runAction(sar, actionDir, sar.remoteAction),
-			)(ctx)
+			return sar.runAction(sar, actionDir, sar.remoteAction)(ctx)
 		}),
 	)
 }
@@ -144,6 +137,19 @@ func (sar *stepActionRemote) getRunContext() *RunContext {
 	return sar.RunContext
 }
 
+func (sar *stepActionRemote) getGithubContext(ctx context.Context) *model.GithubContext {
+	ghc := sar.getRunContext().getGithubContext(ctx)
+
+	// extend github context if we already have an initialized remoteAction
+	remoteAction := sar.remoteAction
+	if remoteAction != nil {
+		ghc.ActionRepository = fmt.Sprintf("%s/%s", remoteAction.Org, remoteAction.Repo)
+		ghc.ActionRef = remoteAction.Ref
+	}
+
+	return ghc
+}
+
 func (sar *stepActionRemote) getStepModel() *model.Step {
 	return sar.Step
 }
@@ -155,7 +161,7 @@ func (sar *stepActionRemote) getEnv() *map[string]string {
 func (sar *stepActionRemote) getIfExpression(ctx context.Context, stage stepStage) string {
 	switch stage {
 	case stepStagePre:
-		github := sar.RunContext.getGithubContext(ctx)
+		github := sar.getGithubContext(ctx)
 		if sar.remoteAction.IsCheckout() && isLocalCheckout(github, sar.Step) && !sar.RunContext.Config.NoSkipCheckout {
 			// skip local checkout pre step
 			return "false"
diff --git a/pkg/runner/step_docker.go b/pkg/runner/step_docker.go
index 6d625ee773d..4ac3b27f054 100644
--- a/pkg/runner/step_docker.go
+++ b/pkg/runner/step_docker.go
@@ -39,6 +39,10 @@ func (sd *stepDocker) getRunContext() *RunContext {
 	return sd.RunContext
 }
 
+func (sd *stepDocker) getGithubContext(ctx context.Context) *model.GithubContext {
+	return sd.getRunContext().getGithubContext(ctx)
+}
+
 func (sd *stepDocker) getStepModel() *model.Step {
 	return sd.Step
 }
diff --git a/pkg/runner/step_run.go b/pkg/runner/step_run.go
index 87c9516a4e1..85dfed6402f 100644
--- a/pkg/runner/step_run.go
+++ b/pkg/runner/step_run.go
@@ -45,6 +45,10 @@ func (sr *stepRun) getRunContext() *RunContext {
 	return sr.RunContext
 }
 
+func (sr *stepRun) getGithubContext(ctx context.Context) *model.GithubContext {
+	return sr.getRunContext().getGithubContext(ctx)
+}
+
 func (sr *stepRun) getStepModel() *model.Step {
 	return sr.Step
 }


Test patch:
diff --git a/pkg/runner/action_test.go b/pkg/runner/action_test.go
index 733daa9e0ce..0b230855f3f 100644
--- a/pkg/runner/action_test.go
+++ b/pkg/runner/action_test.go
@@ -187,10 +187,8 @@ func TestActionRunner(t *testing.T) {
 					Uses: "org/repo/path@ref",
 				},
 				RunContext: &RunContext{
-					ActionRepository: "org/repo",
-					ActionPath:       "path",
-					ActionRef:        "ref",
-					Config:           &Config{},
+					ActionPath: "path",
+					Config:     &Config{},
 					Run: &model.Run{
 						JobID: "job",
 						Workflow: &model.Workflow{
diff --git a/pkg/runner/step_test.go b/pkg/runner/step_test.go
index 621f87358e7..b72f9975fb3 100644
--- a/pkg/runner/step_test.go
+++ b/pkg/runner/step_test.go
@@ -94,6 +94,11 @@ func (sm *stepMock) getRunContext() *RunContext {
 	return args.Get(0).(*RunContext)
 }
 
+func (sm *stepMock) getGithubContext(ctx context.Context) *model.GithubContext {
+	args := sm.Called()
+	return args.Get(0).(*RunContext).getGithubContext(ctx)
+}
+
 func (sm *stepMock) getStepModel() *model.Step {
 	args := sm.Called()
 	return args.Get(0).(*model.Step)
@@ -142,6 +147,7 @@ func TestSetupEnv(t *testing.T) {
 	}
 
 	sm.On("getRunContext").Return(rc)
+	sm.On("getGithubContext").Return(rc)
 	sm.On("getStepModel").Return(step)
 	sm.On("getEnv").Return(&env)
 
diff --git a/pkg/runner/testdata/uses-composite-with-inputs/action/action.yml b/pkg/runner/testdata/uses-composite-with-inputs/action/action.yml
new file mode 100644
index 00000000000..0e3547546ac
--- /dev/null
+++ b/pkg/runner/testdata/uses-composite-with-inputs/action/action.yml
@@ -0,0 +1,19 @@
+name: "action"
+description: "action"
+
+inputs:
+  some:
+    description: "some input"
+    required: true
+outputs:
+  out:
+    description: "some output"
+    value: "output value"
+
+runs:
+  using: "composite"
+  steps:
+    - run: |
+        echo "action input=${{ inputs.some }}"
+        [[ "${{ inputs.some == 'value' }}" = "true" ]] || exit 1
+      shell: bash
diff --git a/pkg/runner/testdata/uses-composite-with-inputs/composite/action.yml b/pkg/runner/testdata/uses-composite-with-inputs/composite/action.yml
new file mode 100644
index 00000000000..4ca6f6dcee0
--- /dev/null
+++ b/pkg/runner/testdata/uses-composite-with-inputs/composite/action.yml
@@ -0,0 +1,22 @@
+name: "composite"
+description: "composite"
+
+inputs:
+  composite-input:
+    description: "value"
+    required: true
+
+runs:
+  using: "composite"
+  steps:
+    - name: test input value
+      run: |
+        echo "input value 1=${{ inputs.composite-input }}"
+        [[ "${{ inputs.composite-input == 'value' }}" = "true" ]] || exit 1
+      shell: bash
+    - uses: nektos/act-test-actions/js@main
+    - name: test input value again
+      run: |
+        echo "input value 2=${{ inputs.composite-input }}"
+        [[ "${{ inputs.composite-input == 'value' }}" = "true" ]] || exit 1
+      shell: bash
diff --git a/pkg/runner/testdata/uses-composite-with-inputs/push.yml b/pkg/runner/testdata/uses-composite-with-inputs/push.yml
new file mode 100644
index 00000000000..229969ba018
--- /dev/null
+++ b/pkg/runner/testdata/uses-composite-with-inputs/push.yml
@@ -0,0 +1,30 @@
+name: push
+on: push
+jobs:
+  test:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v2
+    - name: use simple composite action
+      uses: ./uses-composite-with-inputs/action
+      with:
+        some: value
+    - name: use nested composite action
+      uses: ./uses-composite-with-inputs/composite
+      with:
+        composite-input: value
+    ###
+    #
+    # Remote composite test
+    #
+    - name: use remote composite action
+      id: remote-composite
+      uses: nektos/act-test-actions/composite@main
+      with:
+        input: value
+    - name: test remote composite output
+      run: |
+        echo "steps.remote-composite.outputs.output=${{ steps.remote-composite.outputs.output }}"
+        [[ "${{ steps.remote-composite.outputs.output == 'value' }}" = "true" ]] || exit 1
+    #
+    ###
diff --git a/pkg/runner/testdata/uses-composite/composite_action/action.yml b/pkg/runner/testdata/uses-composite/composite_action/action.yml
index e7b878febe8..e79736c3faa 100644
--- a/pkg/runner/testdata/uses-composite/composite_action/action.yml
+++ b/pkg/runner/testdata/uses-composite/composite_action/action.yml
@@ -35,7 +35,8 @@ outputs:
 runs:
   using: "composite"
   steps:
-    - run: |
+    - name: echo inputs
+      run: |
         echo "#####################################"
         echo "Inputs:"
         echo "---"
diff --git a/pkg/runner/testdata/uses-nested-composite/composite_action2/action.yml b/pkg/runner/testdata/uses-nested-composite/composite_action2/action.yml
index 1062ebd34ff..2fae40cd6da 100644
--- a/pkg/runner/testdata/uses-nested-composite/composite_action2/action.yml
+++ b/pkg/runner/testdata/uses-nested-composite/composite_action2/action.yml
@@ -46,7 +46,8 @@ runs:
       [[ "${{steps.composite.outputs.secret_output == format('{0}/{0}', inputs.test_input_optional)}}" = "true" ]] || exit 1
     shell: bash
   # Now test again with default values
-  - uses: ./uses-composite/composite_action
+  - name: ./uses-composite/composite_action with defaults
+    uses: ./uses-composite/composite_action
     id: composite2
     with:
       test_input_required: 'test_input_required_value'
@@ -60,4 +61,4 @@ runs:
   - run: |
       echo "steps.composite.outputs.secret_output=$COMPOSITE_ACTION_ENV_OUTPUT"
       [[ "${{env.COMPOSITE_ACTION_ENV_OUTPUT == 'my test value' }}" = "true" ]] || exit 1
-    shell: bash
\ No newline at end of file
+    shell: bash